ldd <- read.csv("https://raw.githubusercontent.com/sameralzaim/W02/refs/heads/main/BankLoanDefaultDataset.csv")

1 Introduction

  • Loan Default Data - aim at analyzing loan default trends and attributes in order to enhance future loans underwriting.
  • Data Collection Process - Process of generating the data is unknown,however, it is most likely collected from lending company nor from credit bureau.

1.1 Description of Data

  • Data Structure - dataset contains 1000 observations with 16 categorical and numerical variables.

1.2 Itemized List of Feature Variables

Variable Description Class
Default Shows if account in good standing Cat
Checking_amount Checking amount chr
Term Loan Term num
Credit_score Customer Credit Score num
Gender Customer Gender cat
Marital_status Customer Marital Status cat
Car_loan Car loan (1 yes, 0 No) cat
Personal_loan Personal loan (1 yes, 0 No) cat
Home_loan Home loan (1 yes, 0 No) cat
Education_loan student loan (1 yes, 0 No) cat
Emp_status Employment status (1 employed,0 Not cat
Amount Amount num
Saving_amount Saving Amount num
Emp_duration Number of years employed num
Age Customer Age num
No_of_credit_acc No. of loans the custom have num

1.3 Purposes of Using This Data Set

  • Look into visual presentation of data to provide better understand of it and highlight insights into the outcome that can be driven from the data.

Problem Statements: Report aim to analyse Loan Default Dataset to estimate the probaility of an accounts going into default using other variables as predictor and henc enhance loans underwriting.

1.4 Missing Data

No Missing data in the datset.

# sum (is.na(data))

# colSums(is.na(ldd))

1.5 Accounts Distribution

The report aim to look at the accounts distribution by different demographic in order to identify outliers and asses the impact and correlation between demographic characteristics and probability of account going into default.

library(gridExtra)

layout(matrix(1:8, nrow = 2), widths = c(1, 1, 1,1))

# Create a frequency table Gender
default_plot <- ggplot(ldd, aes(x = Default, fill = Default)) +
  geom_bar(fill = "skyblue") +
  labs(title = "Distribution by Default", x = "Default", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")


gender_plot <- ggplot(ldd, aes(x = Gender, fill = Gender)) +
  geom_bar(fill = "skyblue") +
  labs(title = "Distribution by Gender", x = "Gender", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")


emp_status_plot <- ggplot(ldd, aes(x = Emp_status, fill = Emp_status)) +
  geom_bar(fill = "skyblue") +
  labs(title = "Distribution by Emp Status", x = "Employment Status", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")

data <- ldd %>%
  mutate(Age_Group = cut(Age, 
                         breaks = c(18, 25, 35, 45, 55, 65, Inf), 
                         labels = c("18-25", "26-35", "36-45", "46-55", "56-65", "65+"),
                         right = FALSE))  # Right = FALSE means 25 is in "18-25"

# Create bar plot for Age Group
age_plot <- ggplot(data, aes(x = Age_Group, fill = Age_Group)) +
  geom_bar(fill = "skyblue") +
  labs(title = "No. of Acct by Age",
       x = "Age Group",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

# Group Employment Duration into bins
data <- ldd %>%
  mutate(Emp_duration = cut(Emp_duration, 
                            breaks = c(0, 12, 24, 48, 96, 120, Inf), 
                            labels = c("< 1", "1-2", "2-4", "4-8", "8-10", "10+"),
                            right = FALSE))  # Right = FALSE means 24 is in "12-24"

# Create bar plot for Employment Duration
emp_plot <- ggplot(data, aes(x = Emp_duration, fill = Emp_duration)) +
  geom_bar(fill = "navy") +
  labs(title = "No. of Acct by Emp Years",
       x = "Employment Years",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

data <- ldd %>%
  mutate(No_of_credit_acc = cut(No_of_credit_acc, 
                         breaks = c(1, 3, 5, 7, Inf), 
                         labels = c("1", "1-3", "3-5", "7+"),
                         right = FALSE))  # Right = FALSE means 25 is in "18-25"

# Create bar plot for No. of credit accounts
credit_plot <- ggplot(data, aes(x = No_of_credit_acc, fill = No_of_credit_acc)) +
  geom_bar(fill = "navy") +
  labs(title = "No. of Acct by No of credit acc",
       x = "No of Credit Acc",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

# Create bar plot for Loan Term
term_plot <- ggplot(ldd, aes(x = Term, fill = Term)) +
  geom_bar(fill = "navy") +
  labs(title = "No. of Acct by Loan Term",
       x = "Loan Term",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

# Bar plot for Marital Status
marital_plot <- ggplot(ldd, aes(x = Marital_status)) +
  geom_bar(fill = "navy", color = "navy") +
  labs(title = "No. of Acct by Marital Status",
       x = "Marital Status",
       y = "Count") +
  theme_minimal()+
   theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

grid.arrange(default_plot, gender_plot, emp_status_plot, age_plot, emp_plot, credit_plot, term_plot,  marital_plot, ncol = 4)

🟦 Default: Data shows high number of defaulted accounts.

🟦 Gender: We can see concentration in Males compared to females.

🟦 Employment status: ~ 30% of accounts are unemployed.

🟦 Age Groups: Distribution of accounts Age group shows concentration in age group 26-35 where majority of accounts, ~ 70% concentration in that age group.

🟩 Employment Years: Accounts spred across employments years between < 1 year and up to 10 years with very few have more than 10 years.

🟩 Number of Credit Accounts: Majority of accounts have 1 credit account only, ~ 60+%.

🟩 Loan Terms: Accounts shows normal distribution by loan terms.

🟩 Marital Status: No concentration can be seen by marital status, though more married individuals than singles.

library(gridExtra)

layout(matrix(1:8, nrow = 2), widths = c(1, 1, 1,1))

# Create a frequency table Gender
Car_loan_plot <- ggplot(ldd, aes(x = Car_loan, fill = Car_loan)) +
  geom_bar(fill = "skyblue", color = "skyblue") +
  labs(title = "Distribution by Car_loan", x = "Car_loan", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")

# Create a frequency table Gender
Personal_loan_plot <- ggplot(ldd, aes(x = Personal_loan, fill = Personal_loan)) +
  geom_bar(fill = "skyblue", color = "skyblue") +
  labs(title = "Distribution by Personal_loan", x = "Personal_loan", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")

# Create a frequency table Gender
Personal_loan_plot <- ggplot(ldd, aes(x = Personal_loan, fill = Personal_loan)) +
  geom_bar(fill = "skyblue", color = "skyblue") +
  labs(title = "Distribution by Personal_loan", x = "Personal_loan", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")


# Create a frequency table Gender
Home_loan_plot <- ggplot(ldd, aes(x = Home_loan, fill = Home_loan)) +
  geom_bar(fill = "skyblue", color = "skyblue") +
  labs(title = "Distribution by Home_loan", x = "Home_loan", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")

# Create a frequency table Gender
Education_loan_plot <- ggplot(ldd, aes(x = Education_loan, fill = Education_loan)) +
  geom_bar(fill = "skyblue", color = "skyblue") +
  labs(title = "Distribution by Education_loan", x = "Education_loan", y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5), legend.position = "none")

# Group Checking_amount into bins
data <- ldd %>%
  mutate(Checking_amount = cut(Checking_amount, 
                            breaks = c(-10000, 0, 1000, 2000, 3000, Inf), 
                            labels = c("<0", "0-1K", "1-2K", "2-3K", "4K+"),
                            right = FALSE))  # Right = FALSE means 24 is in "12-24"

# Create bar plot for Checking_amount Duration
Checking_amount_plot <- ggplot(data, aes(x = Checking_amount, fill = Checking_amount)) +
  geom_bar(fill = "navy") +
  labs(title = "Distributiont by Checking_amount",
       x = "Checking_amount",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

# Group Credit_score into bins
data <- ldd %>%
  mutate(Credit_score = cut(Credit_score, 
                            breaks = c(0, 660, 720, Inf), 
                            labels = c("< 660", "660-720", "720+"),
                            right = FALSE))  # Right = FALSE means 600 is in "600-660"

# Create bar plot for Credit_score Duration
Credit_scoret_plot <- ggplot(data, aes(x = Credit_score, fill = Credit_score)) +
  geom_bar(fill = "navy") +
  labs(title = "Distributiont by Credit_score",
       x = "Credit_score",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend

# Group Saving_amount into bins
data <- ldd %>%
  mutate(Saving_amount = cut(Saving_amount, 
                            breaks = c(0, 2000, 3000, 4000, Inf), 
                            labels = c("< 2K", "2-3K", "3-4K","4K+"),
                            right = FALSE))  # Right = FALSE means 600 is in "600-660"

# Create bar plot for Saving_amount Duration
Saving_amount_plot <- ggplot(data, aes(x = Saving_amount, fill = Saving_amount)) +
  geom_bar(fill = "navy") +
  labs(title = "Distributiont by Saving_amount",
       x = "Saving_amount",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend


# Group Amount into bins
data <- ldd %>%
  mutate(Amount = cut(Amount, 
                            breaks = c(0, 500, 1000, Inf), 
                            labels = c("< 0.5K", "0.5-1K", "1K+"),
                            right = FALSE))  # Right = FALSE means 600 is in "600-660"

# Create bar plot for Amount Duration
Amount_plot <- ggplot(data, aes(x = Amount, fill = Amount)) +
  geom_bar(fill = "navy") +
  labs(title = "Distributiont by Amount",
       x = "Amount",
       y = "Count") +
  theme_minimal() +
  theme(plot.title = element_text(size = 9, face = "bold", hjust = 0.5), legend.position = "none")  # Remove redundant legend


grid.arrange( Car_loan_plot, Personal_loan_plot, Home_loan_plot, Education_loan_plot, Checking_amount_plot, Credit_scoret_plot, Saving_amount_plot, Amount_plot, ncol = 4)

🟦 Car Loans: over 60% of accounts have car loans

🟧 Personal Loans: ~ 50% of accounts maintains personal loans.

🟩 Home Loans: More than 90% of accounts have home loans.

🟥 Education Loans: Significant potion of the accounts still have education loans with outstanding balance (~ 80%).

🟦 Checking Accounts Balance: Majority f accounts maintained between $ 0 and 1000 balance in their accounts.

🟧 Credit Score: Almost 80% of accounts have credit score of 720+.

🟩 Saving Amount: Saving accounts balance distributed between $ 2-3K (~ 30%) and $ 3-4K (~ 70%).

🟥 Amount: ~ 20% of accounts have amount between $ 500-1000 and remaining having amount amouns greater than $ 1000 without any accounts with amount greater than $ 2000.

1.6 outliers

Inspecting continuous / numeric arable for potential outliers

# Load required library
library(plotly)

# Create individual interactive boxplots
p1 <- plot_ly(ldd, y = ~Checking_amount, type = "box", name = "Checking Amount", boxpoints = "outliers", marker = list(color = "brown2"))
p2 <- plot_ly(ldd, y = ~Credit_score, type = "box", name = "Credit Score", boxpoints = "outliers", marker = list(color = "purple1"))
p3 <- plot_ly(ldd, y = ~Saving_amount, type = "box", name = "Saving Amount", boxpoints = "outliers", marker = list(color = "cyan4"))
p4 <- plot_ly(ldd, y = ~Amount, type = "box", name = "Amount", boxpoints = "outliers", marker = list(color = "brown2"))


# Arrange them in a single row using subplot
subplot(p1, p2, p3, p4, nrows = 1, shareY = FALSE, titleX = TRUE)

🟦 Checking Amount: We can see few outliers with checking account balance below negative 500 and few > 1000 but nothing too far to skew the data

🟧 Credit Score: It is noticeable that we have many accounts with credit score below 600. These could impact / indicate the probability of default.

🟩 Saving Amount: while we can see few outliers, nothing stands out.

🟥 Amount: Similar to saving amount, nothing stands out

2 Predicting Prbability of Default using Perceptron Classification

We used the perceptron classification with activation sigmoid since it is equivalent to the classical binary logistic regression mode.

The perceptron classification building process follows the following steps:

  • Feature scaling and data splitting
  • Hyperparameter tuning
  • Final model training with the optimal combination of hyperparameter values
  • Prediction and performance evaluation
  • Implementation: Determining the optimal threshold based on appropriate performance metrics

2.1 Feature scaling and data splitting

Data was split 70/30 after converting all categorical variables into numeric variables. then we scaled the variables except the response variable “Default”

ldd <- read.csv("https://raw.githubusercontent.com/sameralzaim/W02/refs/heads/main/BankLoanDefaultDataset.csv")

# Identify character (categorical) variables across column
categorical.vars <- sapply(ldd, is.character)   # test for character...


# One-hot encode categorical variables: creates a full set of dummy variables 
# (i.e. less than full rank parameterization)
dummies <- dummyVars(" ~ . ", data = ldd[, categorical.vars])

## The following predict function produces a data matrix
categorical.encoded <- predict(dummies, newdata = ldd[, categorical.vars])
categorical.encoded <- as.data.frame(categorical.encoded)

names(categorical.encoded)[names(categorical.encoded) == "GenderFemale"] <- "GenderFemale"
# renames variable names to replace "-" with "."
names(categorical.encoded)[names(categorical.encoded) == "GenderMale"] <- "GenderMale"


# Combine with numeric variables (which don't need encoding)
numeric.vars <- ldd[, !categorical.vars ]

processed.data <- cbind(numeric.vars, categorical.encoded)


# Scale numeric variables (excluding the target)
numeric.cols <- sapply(processed.data, is.numeric) & names(processed.data) != "Default"
## by default, scale() takes 'z-score' transformation
processed.data[, numeric.cols] <- scale(processed.data[, numeric.cols])


# Check for missing values: since neuralnet() does not handle missing values
#sum(is.na(processed.data))


# Split data into training and testing sets
set.seed(123)

# sample size
nn <- length(processed.data$Default)
train.index.cls <- sample(1:nn, round(0.7*nn))   # random obs ID
train.data.cls <- processed.data[train.index.cls, ]
test.data.cls <- processed.data[-train.index.cls, ]

##
train.data.orig <- ldd[train.index.cls, ]
test.data.orig <- ldd[-train.index.cls, ]

#table(train.data.cls$Default)
#table(test.data.cls$Default)
#str (processed.data)
#table(train.data.cls$Default)
#table(test.data.cls$Default)

2.2 Hyperparameter Tuning

Have used learning rate of (0.001, 0.01, 0.05, 0.1, 0.2) with stopping threshold of (0.1,0.05) after attempting multiple differnt learning rates and stopping thresholds in order to improve the model accuracy.

During the tuning process, performed 5-fold cross-validation to obtain a stable accuracy score, using a default threshold of 0.5 (for the sigmoid perceptron)

## Grid Search Setup
# Define the hyperparameter grid
hyper.grid.cls <- expand.grid(
  learningrate = c(0.001, 0.01, 0.05, 0.1, 0.2),
  threshold = c(0.01, 0.05)  # Stopping threshold for partial derivatives
 )

# Create formula for neural network
formula <- as.formula(paste("Default ~", paste(names(train.data.cls)[!names(train.data.cls) %in% "Default"], collapse = " + ")))

# Set up 5-fold cross-validation: createFolds() returns a list of fold obs IDs
# returnTrain = FALSE => no print out
#folds <- createFolds(train.data.cls$y, k = 5, list = TRUE, returnTrain = FALSE)

##

k <- 5
fold.size <- floor(dim(train.data.cls)[1]/k)
# Initialize results storage
results <- data.frame(
  learningrate = numeric(),
  threshold = numeric(),
  accuracy = numeric(),
  stringsAsFactors = FALSE
)

## Perform Grid Search with Cross-Validation
for(i in 1:nrow(hyper.grid.cls)) {
  lr <- hyper.grid.cls$learningrate[i]
  th <- hyper.grid.cls$threshold[i]
  
  fold.accuracies <- numeric(k)


## Perform Grid Search with Cross-Validation
for(fold in 1:k) {
    # Split into training and validation sets
    valid.indices <- (1 + (fold-1)*fold.size):(fold*fold.size)
    train.fold <- train.data.cls[-valid.indices, ]
    valid.fold <- train.data.cls[valid.indices, ]
}

    # Train the perceptron
    set.seed(123)
   model.sigmoid <- neuralnet(
  formula,
  data = train.fold,
  hidden = 0,  # Start with 1 hidden unit
  linear.output = FALSE,
  learningrate = lr,
  act.fct = "logistic",
  algorithm = "rprop+",  # Resilient Backpropagation
  threshold = th,
  stepmax = 1e5
)

# Make predictions
preds <- predict(model.sigmoid, valid.fold)

#roc.sigmoid <- roc(valid.fold$Default, preds)
#best.threshold <- coords(roc.sigmoid, "best", ret = "threshold")

#preds <- as.vector(preds)

#pred.class <- ifelse(preds > best.threshold, 1, 0)
pred.classes <- ifelse(preds > 0.4, 1, 0)  # default threshold 0.5

#print (preds)
#print (pred.classes)

# Accuracy
fold.accuracies[fold] <- mean(pred.classes == valid.fold$Default)

#print (pred.classes)
#print (fold_accuracy)


  # Store average accuracy for this hyperparameter combination
  results <- rbind(results, data.frame(
    learningrate = lr,
    threshold = th,
    accuracy = mean(fold.accuracies)
    
  ))
}
#print (results)

## Analyze Results
# Find the best combination
best.combination <- results[which.max(results$accuracy), ]

#cat("\nBest hyperparameter combination:\n")
pander(best.combination)
  learningrate threshold accuracy
6 0.001 0.05 0.1914
#str (preds)
#str(pred.classes)
#table (pred.classes)
#table (valid.fold$Default)
#str(valid.fold$Default)
#table (fold.accuracies)
# Convert preds to a numeric vector (if needed)
#preds <- as.vector(preds)
#table(train.data.cls$Default)
#prop.table(table(train.data.cls$Default))
#summary(preds)
hist(preds, main="Neural Network Prediction Distribution", col="skyblue")

The above identified ‘optimal’ combination of hyperparameters, showed that model accuracy is low. this driven mainly by imbalance in the response variable (30% default). We will continue with training the final perceptron model to see the model separation power.

2.3 Training Final Model

Have used neuralnet() to train the final perceptron model with the tuned hyperparameters and the following assumptions:

  • Model formula: The formula must include scaled numerical feature variables.
  • hidden = 0: This ensures the model is a perceptron (no hidden layers).
  • linear.output = FALSE: Required for classification tasks.
  • The activation function used was logistic .
  • algorithm =: (rprop) used but used resilient rprop+ for training the final model
## Train Final Model with Best Hyperparameters
final.sigmoid.model <- neuralnet(
  formula,
  data = train.data.cls,
  hidden = 0,
  linear.output = FALSE,
  learningrate = best.combination$learningrate,
  threshold = best.combination$threshold,
  act.fct = "logistic",
  algorithm = "rprop+", # The resilient backpropagation with weight backtracking
  stepmax = 1e5
)
# Plot the final model
 plot(final.sigmoid.model)

The model used 18 scaled numerical variables with different power.

#str(test.data.cls) 

2.4 ROC Analysis and Comparison

##
## Evaluate on Test Set
pred.sigmoid <- predict(final.sigmoid.model, test.data.cls)

###  logistic regression
logit.fit <- glm(Default ~ ., data = train.data.cls, family = binomial)
AIC.logit <- step(logit.fit, direction = "both", trace = 0)
pred.logit <- predict(AIC.logit, test.data.cls, type = "response")
pred.full <- predict(logit.fit, test.data.cls, type = "response")

## roc

roc.full.logit <- roc(test.data.cls$Default, pred.full)
roc.AIC.logit <- roc(test.data.cls$Default, pred.logit)
roc.sigmoid <- roc(test.data.cls$Default, pred.sigmoid )

## AUC
auc.sigmoid <- roc.sigmoid$auc
auc.full.logit <- roc.full.logit$auc
auc.AIC.logit <- roc.AIC.logit$auc

## spe-sen
sigmoid.spe <- roc.sigmoid$specificities
sigmoid.sen <- roc.sigmoid$sensitivities

full.logit.spe <- roc.full.logit$specificities
full.logit.sen <- roc.full.logit$sensitivities

AIC.logit.spe <- roc.AIC.logit$specificities
AIC.logit.sen <- roc.AIC.logit$sensitivities

# ROC curve
plot(1-sigmoid.spe, sigmoid.sen, col = "navy", type = "l", lty = 1,
     xlab = "1 - specificity",
     ylab = "sensitivity",
     main = "ROC Curves of Perceptron and Logistic Models")
lines(1-full.logit.spe, full.logit.sen, lty = 1, col = "cyan4")
lines(1-AIC.logit.spe, AIC.logit.sen, lty = 1, col = "cyan")
abline(0,1, lty =2, col = "red")
text(0.98, 0.3, paste("Perceptron AUC = ", round(auc.sigmoid,4)), col = "navy", cex = 0.8, pos = 2)
text(0.98, 0.25, paste("Full Logit AUC = ", round(auc.full.logit,4)), col = "cyan4", cex = 0.8, pos = 2)
text(0.98, 0.2, paste("AIC AUC = ", round(auc.AIC.logit,4)), col = "cyan", cex = 0.8, pos = 2)

The ROC curves above show that the three candidate models perform similarly. All 3 models showing AUC higher than 0.98 though the accuracy < 0.2.

2.5 Perceptron Clasification Concluding Remarks

Perceptron classification with an identity activation function, the model is equivalent or slightly inferior to linear regression

Considering the performance of the above model, where we are seeing mismatch between accuracy and AUC, we will try predicting the default utilizing multilayer Neural Network to see if we can get better / more consistent prediction

3 Mulilayer Neural Network

3.1 Scalling the data

using different scaling / normalization to compare both results.

# Load necessary libraries
# library(neuralnet)
# library(pROC)     # For ROC analysis
# library(ggplot2)  # For visualization

## No missing values in the dataset.

# Feature scaling - normalize numeric variables to [0,1] range
normalize <- function(x) {
  return ((x - min(x)) / (max(x) - min(x)))
}

# Apply normalization to all numeric columns
numeric.cols <- sapply(processed.data, is.numeric)
processed.data[numeric.cols] <- lapply(processed.data[numeric.cols], normalize)

# Two-way data splitting: 70-30%
set.seed(123)  # For reproducibility
sample.size.cls <- floor(0.70 * nrow(processed.data))
train.indices.cls <- sample(1:sample.size.cls, size = sample.size.cls, replace = FALSE)

train.data.cls <- processed.data[train.indices.cls, ]
test.data.cls <- processed.data[-train.indices.cls, ]

To simplify hyperparameter tuning and final model training with the pre-selected MLP architecture for classification, we define a custom function to determine the optimal number of nodes for both single-hidden-layer and double-hidden-layer MLPs

# Function to perform grid search for neuralnet
neuralnet.grid.search <- function(train.data, test.data, hidden.layers = 1) {
  # Define the grid of hyperparameters
  if (hidden.layers == 1) {
    hidden.nodes <- c(2, 4, 6, 8, 10)
    grid <- expand.grid(hidden = hidden.nodes)
  } else {
    hidden.nodes <- c(2, 4, 6)
    grid <- expand.grid(hidden1 = hidden.nodes, hidden2 = hidden.nodes)
  }
  
  # Add columns to store results
  grid$accuracy <- NA
  grid$auc <- NA
  
  # Formula for neural network
  nn.formula <- as.formula(paste("Default ~", 
                                paste(names(train.data)[!names(train.data) %in% "Default"], 
                                      collapse = " + ")))
  
  # Perform grid search
  for (i in 1:nrow(grid)) {
    if (hidden.layers == 1) {
      hidden <- c(grid$hidden[i])
    } else {
      hidden <- c(grid$hidden1[i], grid$hidden2[i])
    }
    
    # Train the model
    nn.model <- neuralnet(
      formula = nn.formula,
      data = train.data,
      hidden = hidden,
      linear.output = FALSE,  # For classification
      act.fct = "logistic",   # Sigmoid activation
      stepmax = 1e6           # Increase max steps for convergence
    )
    
    # Make predictions
    predictions <- predict(nn.model, test.data)
    predicted.classes <- ifelse(predictions > 0.5, 1, 0)
    
    # Calculate accuracy
    accuracy <- mean(predicted.classes == test.data$Default)
    
    # Calculate AUC
    roc.obj <- roc(test.data$Default, predictions)
    auc.value <- auc(roc.obj)
    
    # Store results
    grid$accuracy[i] <- accuracy
    grid$auc[i] <- auc.value
  }
  return(grid)
}

3.2 Accuracy and AUC with One Hidden Layer

we perform one-layer and check the AUD and accuracy values for each number of nods.

# Perform grid search for single hidden layer
grid.results.1layer <- neuralnet.grid.search(train.data=train.data.cls, 
                                             test.data=test.data.cls, 
                                             hidden.layers = 1)
pander(grid.results.1layer)
hidden accuracy auc
2 0.9067 0.9671
4 0.9033 0.9653
6 0.8833 0.9599
8 0.8667 0.9489
10 0.92 0.9576

The optimal number of nodes in the hidden layer is the corresponds to the smallest AUC. Similarly, the performance table of two-hidden-layer MLP is given below.

3.3 Accuracy and AUC with Two Hiden Layer

We replicate the above step but with 2 hidden layers

# Perform grid search for two hidden layers
grid.results.2layer <- neuralnet.grid.search(train.data=train.data.cls, 
                                             test.data=test.data.cls, 
                                             hidden.layers = 2)
pander(grid.results.2layer)
hidden1 hidden2 accuracy auc
2 2 0.9067 0.9575
4 2 0.8833 0.9513
6 2 0.9 0.9528
2 4 0.9 0.9544
4 4 0.9 0.9542
6 4 0.89 0.9544
2 6 0.92 0.9586
4 6 0.8967 0.9596
6 6 0.88 0.9511

3.4 One-hidden-layer MLP

We use the optimal number of nodes to fit the one-hidden-layer MLP to the data

# Formula for neural network
nn.formula <- as.formula(paste("Default ~", 
                              paste(names(train.data.cls)[!names(train.data.cls) %in% "Default"], 
                                    collapse = " + ")))

# Train single hidden layer model (using best configuration from grid search)
best.1layer <- grid.results.1layer[which.max(grid.results.1layer$auc), ]

nn.1layer <- neuralnet(
  formula = nn.formula,
  data = train.data.cls,
  hidden = best.1layer$hidden,
  linear.output = FALSE,
  act.fct = "logistic",
  stepmax = 1e6
)
##

  plot(nn.1layer, main = paste("One-hidden-layer with", best.1layer$hidden, "Nodes"))

3.5 Two-hidden-layer MLP

We use the optimal number of nodes to fit the Two-Hidden-layer MLP to the data

# Train two hidden layers model (using best configuration from grid search)
best.2layer <- grid.results.2layer[which.max(grid.results.2layer$auc), ]

nn.2layer <- neuralnet(
  formula = nn.formula,
  data = train.data.cls,
  hidden = c(best.2layer$hidden1, best.2layer$hidden2),
  linear.output = FALSE,
  act.fct = "logistic",
  stepmax = 1e6
)
##
plot(nn.2layer)

In the two model plots above, the Error and Steps values displayed at the bottom represent:

Steps: The number of training iterations (epochs) completed during model optimization. Each step corresponds to one complete forward/backward pass and weight update cycle.

Errors: The training error reflects the loss function value (typically SSE for regression or cross-entropy for classification). The displayed Error represents the final error value achieved when the optimization process converges.

Next, we write a custom function to extract the performance metrics to assess the global performance through ROC curves and the corresponding areas under the ROC curves.

# Function to evaluate model performance
evaluate.model <- function(model, test.data) {
  # Make predictions
  predictions <- predict(model, test.data)
  predicted.classes <- ifelse(predictions > 0.5, 1, 0)
  
  # Calculate metrics
  accuracy <- mean(predicted.classes == test.data$Default)
  confusion.matrix <- table(Predicted = predicted.classes, Actual = test.data$Default)
  roc.obj <- roc(test.data$Default, predictions)
  auc.value <- auc(roc.obj)
  
  return(list(
    accuracy = accuracy,
    confusion.matrix = confusion.matrix,
    roc.obj = roc.obj,
    auc = auc.value
  ))
}

# Evaluate single hidden layer model
perf.1layer <- evaluate.model(nn.1layer, test.data.cls)
#print(perf.1layer[c("accuracy", "confusion_matrix", "auc")])

# Evaluate two hidden layers model
perf.2layer <- evaluate.model(nn.2layer, test.data.cls)
#print(perf.2layer[c("accuracy", "confusion_matrix", "auc")])

We use classic logistic regression as the base model and compare it with the two MLPs using ROC curves and their corresponding AUC values in the following figure.

# Train logistic regression model (base model)
logit.model <- glm(Default ~ ., data = train.data.cls, family = binomial)

# Evaluate logistic regression model
logit.pred <- predict(logit.model, test.data.cls, type = "response")
logit.classes <- ifelse(logit.pred > 0.5, 1, 0)
logit.accuracy <- mean(logit.classes == test.data.cls$Default)
logit.roc <- roc(test.data.cls$Default, logit.pred)
logit.auc <- auc(logit.roc)

##
roc.1layer <- perf.1layer$roc.obj
roc.2layer <- perf.2layer$roc.obj
roc.logit <- logit.roc

## specificity and sensitivity
sen.1layer <- roc.1layer$sensitivities
spe.1layer <- roc.1layer$specificities
sen.2layer <- roc.2layer$sensitivities
spe.2layer <- roc.2layer$specificities
sen.logit <- roc.logit$sensitivities
spe.logit <- roc.logit$specificities

## AUC
auc.1layer <- roc.1layer$auc
auc.2layer <- roc.2layer$auc
auc.logit <- roc.logit$auc

## Plot ROC curves for comparison
par(pty = "s")   # make a square plot to avaoid distortion
plot(1-spe.1layer, sen.1layer, type = "l", lty = 1,
     col = "blue", 
     xlab = "1 - specificity",
     ylab = "sensitvity",
     main = "ROC Curve Comparison")

lines(1-spe.2layer, sen.2layer, lty = 1, col = "darkred")
lines(1-spe.logit, sen.logit, lty = 1, col = "darkgreen")
legend("bottomright", 
       legend = c(paste("1-layer MLP (AUC =", round(perf.1layer$auc, 3), ")"),
                  paste("2-layer MLP (AUC =", round(perf.2layer$auc, 3), ")"),
                  paste("Logistic Reg (AUC =", round(logit.auc, 3), ")")),
                col = c("blue", "darkred", "darkgreen"), 
                lty = 1, cex = 0.7, bty = "n")

4 Conclusion

When comparing the different methodologies we can see that the multilayers approach give us the highest accuracy with relation to AUC. additionally comparing One-Hidden_Layer with Two-Hidden-Layers, shows the followings:

  • One-Hidden-Layer
Hidden Accuracy AUC
2 0.9067 0.9671
  • Two-Hidden-Layer
Hidden1 Hidden2 Accuracy AUC
2 6 0.92 0.9586

Comparing the above, we would recommend using multilayers with One Hidden Layer.

LS0tDQp0aXRsZTogIkJhbmtpbmcgRGVmYXVsdCINCmF1dGhvcjogIlNhbWVyIEFsemFpbSINCmRhdGU6ICIgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDMNCiAgICBmaWdfaGVpZ2h0OiAzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQpgYGB7PWh0bWx9DQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIENhc2NhZGluZyBTdHlsZSBTaGVldHMgKENTUykgaXMgYSBzdHlsZXNoZWV0IGxhbmd1YWdlIHVzZWQgdG8gZGVzY3JpYmUgdGhlIHByZXNlbnRhdGlvbiBvZiBhIGRvY3VtZW50IHdyaXR0ZW4gaW4gSFRNTCBvciBYTUwuIGl0IGlzIGEgc2ltcGxlIG1lY2hhbmlzbSBmb3IgYWRkaW5nIHN0eWxlIChlLmcuLCBmb250cywgY29sb3JzLCBzcGFjaW5nKSB0byBXZWIgZG9jdW1lbnRzLiAqLw0KDQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBjb2xvcjogbmF2eTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LWZhbWlseTogIkdpbGwgU2FucyIsIHNhbnMtc2VyaWY7DQp9DQpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGF1dGhvcnMgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IG5hdnk7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciB0aGUgZGF0ZSAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya0JsdWU7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAyMHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDMgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQ0KDQpwIHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQo8L3N0eWxlPg0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQp9DQppZiAoIXJlcXVpcmUoIkdHYWxseSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJHR2FsbHkiKQ0KbGlicmFyeShHR2FsbHkpDQp9DQppZiAoIXJlcXVpcmUoImdsbW5ldCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQ0KbGlicmFyeShnbG1uZXQpDQp9DQppZiAoIXJlcXVpcmUoImNhcmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikNCmxpYnJhcnkoY2FyZXQpDQp9DQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiTUFTUyIpDQpsaWJyYXJ5KE1BU1MpDQp9DQppZiAoIXJlcXVpcmUoIm1sYmVuY2giKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygibWxiZW5jaCIpDQpsaWJyYXJ5KG1sYmVuY2gpDQp9DQppZiAoIXJlcXVpcmUoInBST0MiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQpsaWJyYXJ5KHBST0MpDQp9DQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KbGlicmFyeShwbG90bHkpDQp9DQppZiAoIXJlcXVpcmUoInBhbmRlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwYW5kZXIiKQ0KbGlicmFyeShwYW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoInJhbmRvbUZvcmVzdCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJyYW5kb21Gb3Jlc3QiKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQp9DQoNCmlmICghcmVxdWlyZSgiaXByZWQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiaXByZWQiKQ0KbGlicmFyeShpcHJlZCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpDQpsaWJyYXJ5KHJwYXJ0KQ0KfQ0Kb3B0aW9ucyhyZXBvcyA9IGMoQ1JBTiA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmciKSkNCg0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KbGlicmFyeShycGFydC5wbG90KQ0KfQ0KDQppZiAoIXJlcXVpcmUoImRwbHlyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmxpYnJhcnkoZHBseXIpDQp9DQoNCmlmICghcmVxdWlyZSgibmV1cmFsbmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpDQpsaWJyYXJ5KG5ldXJhbG5ldCkNCn0NCmlmICghcmVxdWlyZSgibmV1cmFsbmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpDQpsaWJyYXJ5KG5ldXJhbG5ldCkNCn0NCmlmICghcmVxdWlyZSgiTmV1cmFsTmV0VG9vbHMiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiTmV1cmFsTmV0VG9vbHMiKQ0KbGlicmFyeShOZXVyYWxOZXRUb29scykNCn0NCiMjIyANCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgICANCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHRzID0gVFJVRSwgICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQQ0KICAgICAgICAgICAgICAgICAgICAgICkgIA0KYGBgDQoNCg0KDQpgYGB7cn0NCmxkZCA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NhbWVyYWx6YWltL1cwMi9yZWZzL2hlYWRzL21haW4vQmFua0xvYW5EZWZhdWx0RGF0YXNldC5jc3YiKQ0KDQpgYGANCiMgSW50cm9kdWN0aW9uDQogICsgKkxvYW4gRGVmYXVsdCBEYXRhKiAtIGFpbSBhdCBhbmFseXppbmcgbG9hbiBkZWZhdWx0IHRyZW5kcyBhbmQgYXR0cmlidXRlcyBpbiBvcmRlciB0byBlbmhhbmNlIGZ1dHVyZSBsb2FucyB1bmRlcndyaXRpbmcuDQogICsgKkRhdGEgQ29sbGVjdGlvbiBQcm9jZXNzKiAtIFByb2Nlc3Mgb2YgZ2VuZXJhdGluZyB0aGUgZGF0YSBpcyB1bmtub3duLGhvd2V2ZXIsIGl0IGlzIG1vc3QgbGlrZWx5IGNvbGxlY3RlZCBmcm9tIGxlbmRpbmcgY29tcGFueSBub3IgZnJvbSBjcmVkaXQgYnVyZWF1Lg0KICAgIA0KIyMgRGVzY3JpcHRpb24gb2YgRGF0YQ0KDQogICsgKkRhdGEgU3RydWN0dXJlKiAtIGRhdGFzZXQgY29udGFpbnMgMTAwMCBvYnNlcnZhdGlvbnMgd2l0aCAxNiBjYXRlZ29yaWNhbCBhbmQgbnVtZXJpY2FsIHZhcmlhYmxlcy4gDQogIA0KIyMgSXRlbWl6ZWQgTGlzdCBvZiBGZWF0dXJlIFZhcmlhYmxlcw0KICANCmBgYHtyLCBlY2hvPUZBTFNFfQ0KDQojc3RyKGxkZCkNCg0KYGBgDQp8ICoqVmFyaWFibGUqKiAgICAgfCAqKkRlc2NyaXB0aW9uKiogICAgICAgICAgfCoqQ2xhc3MqKiB8DQp8Oi0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS18DQp8IERlZmF1bHQgICAgICAgICAgIHwgU2hvd3MgaWYgYWNjb3VudCBpbiBnb29kIHN0YW5kaW5nICB8IENhdCAgICAgIHwNCnwgQ2hlY2tpbmdfYW1vdW50ICAgfCBDaGVja2luZyBhbW91bnQgICAgICAgICAgICAgICAgICAgIHwgY2hyICAgICAgfA0KfCBUZXJtICAgICAgICAgICAgICB8IExvYW4gVGVybSAgICAgICAgICAgICAgICAgICAgICAgICAgfCBudW0gICAgICB8DQp8IENyZWRpdF9zY29yZSAgICAgIHwgQ3VzdG9tZXIgQ3JlZGl0IFNjb3JlICAgICAgICAgICAgICB8IG51bSAgICAgIHwNCnwgR2VuZGVyICAgICAgICAgICAgfCBDdXN0b21lciBHZW5kZXIgICAgICAgICAgICAgICAgICAgIHwgY2F0ICAgICAgfA0KfCBNYXJpdGFsX3N0YXR1cyAgICB8IEN1c3RvbWVyIE1hcml0YWwgU3RhdHVzICAgICAgICAgICAgfCBjYXQgICAgICB8DQp8IENhcl9sb2FuICAgICAgICAgIHwgQ2FyIGxvYW4gKDEgeWVzLCAwIE5vKSAgICAgICAgICAgICB8IGNhdCAgICAgIHwNCnwgUGVyc29uYWxfbG9hbiAgICAgfCBQZXJzb25hbCBsb2FuICgxIHllcywgMCBObykgICAgICAgIHwgY2F0ICAgICAgfA0KfCBIb21lX2xvYW4gICAgICAgICB8IEhvbWUgbG9hbiAoMSB5ZXMsIDAgTm8pICAgICAgICAgICAgfCBjYXQgICAgICB8DQp8IEVkdWNhdGlvbl9sb2FuICAgIHwgc3R1ZGVudCBsb2FuICgxIHllcywgMCBObykgICAgICAgICB8IGNhdCAgICAgIHwNCnwgRW1wX3N0YXR1cyAgICAgICAgfCBFbXBsb3ltZW50IHN0YXR1cyAoMSBlbXBsb3llZCwwIE5vdHwgY2F0ICAgICAgfA0KfCBBbW91bnQgICAgICAgICAgICB8IEFtb3VudCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBudW0gICAgICB8DQp8IFNhdmluZ19hbW91bnQgICAgIHwgU2F2aW5nIEFtb3VudCAgICAgICAgICAgICAgICAgICAgICB8IG51bSAgICAgIHwNCnwgRW1wX2R1cmF0aW9uICAgICAgfCBOdW1iZXIgb2YgeWVhcnMgZW1wbG95ZWQgICAgICAgICAgIHwgbnVtICAgICAgfA0KfCBBZ2UgICAgICAgICAgICAgICB8IEN1c3RvbWVyIEFnZSAgICAgICAgICAgICAgICAgICAgICAgfCBudW0gICAgICB8DQp8IE5vX29mX2NyZWRpdF9hY2MgIHwgTm8uIG9mIGxvYW5zIHRoZSBjdXN0b20gaGF2ZSAgICAgICB8IG51bSAgICAgIHwNCg0KDQogICAgDQoNCiAgDQojIyBQdXJwb3NlcyBvZiBVc2luZyBUaGlzIERhdGEgU2V0DQoNCiAgKyBMb29rIGludG8gdmlzdWFsIHByZXNlbnRhdGlvbiBvZiBkYXRhIHRvIHByb3ZpZGUgYmV0dGVyIHVuZGVyc3RhbmQgb2YgaXQgYW5kIGhpZ2hsaWdodCBpbnNpZ2h0cyBpbnRvIHRoZSBvdXRjb21lIHRoYXQgY2FuIGJlIGRyaXZlbiBmcm9tIHRoZSBkYXRhLg0KICANCiA8Zm9udCBjb2xvciA9ICJyZWQiPioqXGNvbG9ye3JlZH1Qcm9ibGVtIFN0YXRlbWVudHMqKjoqPC9mb250PiAgKiAgPGZvbnQgY29sb3IgPSAiYmx1ZSI+XGNvbG9ye2JsdWV9UmVwb3J0IGFpbSB0byBhbmFseXNlIExvYW4gRGVmYXVsdCBEYXRhc2V0IHRvIGVzdGltYXRlIHRoZSBwcm9iYWlsaXR5IG9mIGFuIGFjY291bnRzIGdvaW5nIGludG8gZGVmYXVsdCB1c2luZyBvdGhlciB2YXJpYWJsZXMgYXMgcHJlZGljdG9yIGFuZCBoZW5jIGVuaGFuY2UgbG9hbnMgdW5kZXJ3cml0aW5nLjwvZm9udD4NCg0KIyMgTWlzc2luZyBEYXRhDQoNCk5vIE1pc3NpbmcgZGF0YSBpbiB0aGUgZGF0c2V0Lg0KDQpgYGB7cn0NCiMgc3VtIChpcy5uYShkYXRhKSkNCg0KIyBjb2xTdW1zKGlzLm5hKGxkZCkpDQpgYGANCg0KIyMgQWNjb3VudHMgRGlzdHJpYnV0aW9uDQoNClRoZSByZXBvcnQgYWltIHRvIGxvb2sgYXQgdGhlIGFjY291bnRzIGRpc3RyaWJ1dGlvbiBieSBkaWZmZXJlbnQgZGVtb2dyYXBoaWMgaW4gb3JkZXIgdG8gaWRlbnRpZnkgb3V0bGllcnMgYW5kIGFzc2VzIHRoZSBpbXBhY3QgYW5kIGNvcnJlbGF0aW9uIGJldHdlZW4gZGVtb2dyYXBoaWMgY2hhcmFjdGVyaXN0aWNzIGFuZCBwcm9iYWJpbGl0eSBvZiBhY2NvdW50IGdvaW5nIGludG8gZGVmYXVsdC4NCg0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCg0KbGF5b3V0KG1hdHJpeCgxOjgsIG5yb3cgPSAyKSwgd2lkdGhzID0gYygxLCAxLCAxLDEpKQ0KDQojIENyZWF0ZSBhIGZyZXF1ZW5jeSB0YWJsZSBHZW5kZXINCmRlZmF1bHRfcGxvdCA8LSBnZ3Bsb3QobGRkLCBhZXMoeCA9IERlZmF1bHQsIGZpbGwgPSBEZWZhdWx0KSkgKw0KICBnZW9tX2JhcihmaWxsID0gInNreWJsdWUiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIGJ5IERlZmF1bHQiLCB4ID0gIkRlZmF1bHQiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCg0KZ2VuZGVyX3Bsb3QgPC0gZ2dwbG90KGxkZCwgYWVzKHggPSBHZW5kZXIsIGZpbGwgPSBHZW5kZXIpKSArDQogIGdlb21fYmFyKGZpbGwgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gYnkgR2VuZGVyIiwgeCA9ICJHZW5kZXIiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCg0KZW1wX3N0YXR1c19wbG90IDwtIGdncGxvdChsZGQsIGFlcyh4ID0gRW1wX3N0YXR1cywgZmlsbCA9IEVtcF9zdGF0dXMpKSArDQogIGdlb21fYmFyKGZpbGwgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gYnkgRW1wIFN0YXR1cyIsIHggPSAiRW1wbG95bWVudCBTdGF0dXMiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCmRhdGEgPC0gbGRkICU+JQ0KICBtdXRhdGUoQWdlX0dyb3VwID0gY3V0KEFnZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygxOCwgMjUsIDM1LCA0NSwgNTUsIDY1LCBJbmYpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIxOC0yNSIsICIyNi0zNSIsICIzNi00NSIsICI0Ni01NSIsICI1Ni02NSIsICI2NSsiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByaWdodCA9IEZBTFNFKSkgICMgUmlnaHQgPSBGQUxTRSBtZWFucyAyNSBpcyBpbiAiMTgtMjUiDQoNCiMgQ3JlYXRlIGJhciBwbG90IGZvciBBZ2UgR3JvdXANCmFnZV9wbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IEFnZV9Hcm91cCwgZmlsbCA9IEFnZV9Hcm91cCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJza3libHVlIikgKw0KICBsYWJzKHRpdGxlID0gIk5vLiBvZiBBY2N0IGJ5IEFnZSIsDQogICAgICAgeCA9ICJBZ2UgR3JvdXAiLA0KICAgICAgIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAgIyBSZW1vdmUgcmVkdW5kYW50IGxlZ2VuZA0KDQojIEdyb3VwIEVtcGxveW1lbnQgRHVyYXRpb24gaW50byBiaW5zDQpkYXRhIDwtIGxkZCAlPiUNCiAgbXV0YXRlKEVtcF9kdXJhdGlvbiA9IGN1dChFbXBfZHVyYXRpb24sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMCwgMTIsIDI0LCA0OCwgOTYsIDEyMCwgSW5mKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiPCAxIiwgIjEtMiIsICIyLTQiLCAiNC04IiwgIjgtMTAiLCAiMTArIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmlnaHQgPSBGQUxTRSkpICAjIFJpZ2h0ID0gRkFMU0UgbWVhbnMgMjQgaXMgaW4gIjEyLTI0Ig0KDQojIENyZWF0ZSBiYXIgcGxvdCBmb3IgRW1wbG95bWVudCBEdXJhdGlvbg0KZW1wX3Bsb3QgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gRW1wX2R1cmF0aW9uLCBmaWxsID0gRW1wX2R1cmF0aW9uKSkgKw0KICBnZW9tX2JhcihmaWxsID0gIm5hdnkiKSArDQogIGxhYnModGl0bGUgPSAiTm8uIG9mIEFjY3QgYnkgRW1wIFllYXJzIiwNCiAgICAgICB4ID0gIkVtcGxveW1lbnQgWWVhcnMiLA0KICAgICAgIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAgIyBSZW1vdmUgcmVkdW5kYW50IGxlZ2VuZA0KDQpkYXRhIDwtIGxkZCAlPiUNCiAgbXV0YXRlKE5vX29mX2NyZWRpdF9hY2MgPSBjdXQoTm9fb2ZfY3JlZGl0X2FjYywgDQogICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygxLCAzLCA1LCA3LCBJbmYpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIxIiwgIjEtMyIsICIzLTUiLCAiNysiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByaWdodCA9IEZBTFNFKSkgICMgUmlnaHQgPSBGQUxTRSBtZWFucyAyNSBpcyBpbiAiMTgtMjUiDQoNCiMgQ3JlYXRlIGJhciBwbG90IGZvciBOby4gb2YgY3JlZGl0IGFjY291bnRzDQpjcmVkaXRfcGxvdCA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBOb19vZl9jcmVkaXRfYWNjLCBmaWxsID0gTm9fb2ZfY3JlZGl0X2FjYykpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJuYXZ5IikgKw0KICBsYWJzKHRpdGxlID0gIk5vLiBvZiBBY2N0IGJ5IE5vIG9mIGNyZWRpdCBhY2MiLA0KICAgICAgIHggPSAiTm8gb2YgQ3JlZGl0IEFjYyIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSByZWR1bmRhbnQgbGVnZW5kDQoNCiMgQ3JlYXRlIGJhciBwbG90IGZvciBMb2FuIFRlcm0NCnRlcm1fcGxvdCA8LSBnZ3Bsb3QobGRkLCBhZXMoeCA9IFRlcm0sIGZpbGwgPSBUZXJtKSkgKw0KICBnZW9tX2JhcihmaWxsID0gIm5hdnkiKSArDQogIGxhYnModGl0bGUgPSAiTm8uIG9mIEFjY3QgYnkgTG9hbiBUZXJtIiwNCiAgICAgICB4ID0gIkxvYW4gVGVybSIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSByZWR1bmRhbnQgbGVnZW5kDQoNCiMgQmFyIHBsb3QgZm9yIE1hcml0YWwgU3RhdHVzDQptYXJpdGFsX3Bsb3QgPC0gZ2dwbG90KGxkZCwgYWVzKHggPSBNYXJpdGFsX3N0YXR1cykpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJuYXZ5IiwgY29sb3IgPSAibmF2eSIpICsNCiAgbGFicyh0aXRsZSA9ICJOby4gb2YgQWNjdCBieSBNYXJpdGFsIFN0YXR1cyIsDQogICAgICAgeCA9ICJNYXJpdGFsIFN0YXR1cyIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSByZWR1bmRhbnQgbGVnZW5kDQoNCmdyaWQuYXJyYW5nZShkZWZhdWx0X3Bsb3QsIGdlbmRlcl9wbG90LCBlbXBfc3RhdHVzX3Bsb3QsIGFnZV9wbG90LCBlbXBfcGxvdCwgY3JlZGl0X3Bsb3QsIHRlcm1fcGxvdCwgIG1hcml0YWxfcGxvdCwgbmNvbCA9IDQpDQoNCmBgYA0KDQogIPCfn6YgRGVmYXVsdDogRGF0YSBzaG93cyBoaWdoIG51bWJlciBvZiBkZWZhdWx0ZWQgYWNjb3VudHMuDQogIA0KICDwn5+mIEdlbmRlcjogV2UgY2FuIHNlZSBjb25jZW50cmF0aW9uIGluIE1hbGVzIGNvbXBhcmVkIHRvIGZlbWFsZXMuDQogIA0KICDwn5+mIEVtcGxveW1lbnQgc3RhdHVzOiAgfiAzMCUgb2YgYWNjb3VudHMgYXJlIHVuZW1wbG95ZWQuDQogIA0KICDwn5+mIEFnZSBHcm91cHM6IERpc3RyaWJ1dGlvbiBvZiBhY2NvdW50cyBBZ2UgZ3JvdXAgc2hvd3MgY29uY2VudHJhdGlvbiBpbiBhZ2UgZ3JvdXAgMjYtMzUgd2hlcmUgbWFqb3JpdHkgb2YgYWNjb3VudHMsIH4gNzAlIGNvbmNlbnRyYXRpb24gaW4gdGhhdCBhZ2UgZ3JvdXAuDQogIA0KICDwn5+pIEVtcGxveW1lbnQgWWVhcnM6IEFjY291bnRzIHNwcmVkIGFjcm9zcyBlbXBsb3ltZW50cyB5ZWFycyBiZXR3ZWVuIDwgMSB5ZWFyIGFuZCB1cCB0byAxMCB5ZWFycyB3aXRoIHZlcnkgZmV3IGhhdmUgbW9yZSB0aGFuIDEwIHllYXJzLg0KICANCiAg8J+fqSBOdW1iZXIgb2YgQ3JlZGl0IEFjY291bnRzOiBNYWpvcml0eSBvZiBhY2NvdW50cyBoYXZlIDEgY3JlZGl0IGFjY291bnQgb25seSwgfiA2MCslLg0KICANCiAg8J+fqSBMb2FuIFRlcm1zOiBBY2NvdW50cyBzaG93cyBub3JtYWwgZGlzdHJpYnV0aW9uIGJ5IGxvYW4gdGVybXMuDQogIA0KICDwn5+pIE1hcml0YWwgU3RhdHVzOiBObyBjb25jZW50cmF0aW9uIGNhbiBiZSBzZWVuIGJ5IG1hcml0YWwgc3RhdHVzLCB0aG91Z2ggbW9yZSBtYXJyaWVkIGluZGl2aWR1YWxzIHRoYW4gc2luZ2xlcy4NCiAgDQogIA0KYGBge3J9DQoNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KDQpsYXlvdXQobWF0cml4KDE6OCwgbnJvdyA9IDIpLCB3aWR0aHMgPSBjKDEsIDEsIDEsMSkpDQoNCiMgQ3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlIEdlbmRlcg0KQ2FyX2xvYW5fcGxvdCA8LSBnZ3Bsb3QobGRkLCBhZXMoeCA9IENhcl9sb2FuLCBmaWxsID0gQ2FyX2xvYW4pKSArDQogIGdlb21fYmFyKGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gInNreWJsdWUiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIGJ5IENhcl9sb2FuIiwgeCA9ICJDYXJfbG9hbiIsIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgR2VuZGVyDQpQZXJzb25hbF9sb2FuX3Bsb3QgPC0gZ2dwbG90KGxkZCwgYWVzKHggPSBQZXJzb25hbF9sb2FuLCBmaWxsID0gUGVyc29uYWxfbG9hbikpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJza3libHVlIiwgY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gYnkgUGVyc29uYWxfbG9hbiIsIHggPSAiUGVyc29uYWxfbG9hbiIsIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgR2VuZGVyDQpQZXJzb25hbF9sb2FuX3Bsb3QgPC0gZ2dwbG90KGxkZCwgYWVzKHggPSBQZXJzb25hbF9sb2FuLCBmaWxsID0gUGVyc29uYWxfbG9hbikpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJza3libHVlIiwgY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gYnkgUGVyc29uYWxfbG9hbiIsIHggPSAiUGVyc29uYWxfbG9hbiIsIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KDQojIENyZWF0ZSBhIGZyZXF1ZW5jeSB0YWJsZSBHZW5kZXINCkhvbWVfbG9hbl9wbG90IDwtIGdncGxvdChsZGQsIGFlcyh4ID0gSG9tZV9sb2FuLCBmaWxsID0gSG9tZV9sb2FuKSkgKw0KICBnZW9tX2JhcihmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJza3libHVlIikgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBieSBIb21lX2xvYW4iLCB4ID0gIkhvbWVfbG9hbiIsIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgR2VuZGVyDQpFZHVjYXRpb25fbG9hbl9wbG90IDwtIGdncGxvdChsZGQsIGFlcyh4ID0gRWR1Y2F0aW9uX2xvYW4sIGZpbGwgPSBFZHVjYXRpb25fbG9hbikpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJza3libHVlIiwgY29sb3IgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gYnkgRWR1Y2F0aW9uX2xvYW4iLCB4ID0gIkVkdWNhdGlvbl9sb2FuIiwgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIEdyb3VwIENoZWNraW5nX2Ftb3VudCBpbnRvIGJpbnMNCmRhdGEgPC0gbGRkICU+JQ0KICBtdXRhdGUoQ2hlY2tpbmdfYW1vdW50ID0gY3V0KENoZWNraW5nX2Ftb3VudCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygtMTAwMDAsIDAsIDEwMDAsIDIwMDAsIDMwMDAsIEluZiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjwwIiwgIjAtMUsiLCAiMS0ySyIsICIyLTNLIiwgIjRLKyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJpZ2h0ID0gRkFMU0UpKSAgIyBSaWdodCA9IEZBTFNFIG1lYW5zIDI0IGlzIGluICIxMi0yNCINCg0KIyBDcmVhdGUgYmFyIHBsb3QgZm9yIENoZWNraW5nX2Ftb3VudCBEdXJhdGlvbg0KQ2hlY2tpbmdfYW1vdW50X3Bsb3QgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gQ2hlY2tpbmdfYW1vdW50LCBmaWxsID0gQ2hlY2tpbmdfYW1vdW50KSkgKw0KICBnZW9tX2JhcihmaWxsID0gIm5hdnkiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9udCBieSBDaGVja2luZ19hbW91bnQiLA0KICAgICAgIHggPSAiQ2hlY2tpbmdfYW1vdW50IiwNCiAgICAgICB5ID0gIkNvdW50IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgICMgUmVtb3ZlIHJlZHVuZGFudCBsZWdlbmQNCg0KIyBHcm91cCBDcmVkaXRfc2NvcmUgaW50byBiaW5zDQpkYXRhIDwtIGxkZCAlPiUNCiAgbXV0YXRlKENyZWRpdF9zY29yZSA9IGN1dChDcmVkaXRfc2NvcmUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMCwgNjYwLCA3MjAsIEluZiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjwgNjYwIiwgIjY2MC03MjAiLCAiNzIwKyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJpZ2h0ID0gRkFMU0UpKSAgIyBSaWdodCA9IEZBTFNFIG1lYW5zIDYwMCBpcyBpbiAiNjAwLTY2MCINCg0KIyBDcmVhdGUgYmFyIHBsb3QgZm9yIENyZWRpdF9zY29yZSBEdXJhdGlvbg0KQ3JlZGl0X3Njb3JldF9wbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IENyZWRpdF9zY29yZSwgZmlsbCA9IENyZWRpdF9zY29yZSkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJuYXZ5IikgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbnQgYnkgQ3JlZGl0X3Njb3JlIiwNCiAgICAgICB4ID0gIkNyZWRpdF9zY29yZSIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSByZWR1bmRhbnQgbGVnZW5kDQoNCiMgR3JvdXAgU2F2aW5nX2Ftb3VudCBpbnRvIGJpbnMNCmRhdGEgPC0gbGRkICU+JQ0KICBtdXRhdGUoU2F2aW5nX2Ftb3VudCA9IGN1dChTYXZpbmdfYW1vdW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDAsIDIwMDAsIDMwMDAsIDQwMDAsIEluZiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjwgMksiLCAiMi0zSyIsICIzLTRLIiwiNEsrIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmlnaHQgPSBGQUxTRSkpICAjIFJpZ2h0ID0gRkFMU0UgbWVhbnMgNjAwIGlzIGluICI2MDAtNjYwIg0KDQojIENyZWF0ZSBiYXIgcGxvdCBmb3IgU2F2aW5nX2Ftb3VudCBEdXJhdGlvbg0KU2F2aW5nX2Ftb3VudF9wbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IFNhdmluZ19hbW91bnQsIGZpbGwgPSBTYXZpbmdfYW1vdW50KSkgKw0KICBnZW9tX2JhcihmaWxsID0gIm5hdnkiKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9udCBieSBTYXZpbmdfYW1vdW50IiwNCiAgICAgICB4ID0gIlNhdmluZ19hbW91bnQiLA0KICAgICAgIHkgPSAiQ291bnQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAgIyBSZW1vdmUgcmVkdW5kYW50IGxlZ2VuZA0KDQoNCiMgR3JvdXAgQW1vdW50IGludG8gYmlucw0KZGF0YSA8LSBsZGQgJT4lDQogIG11dGF0ZShBbW91bnQgPSBjdXQoQW1vdW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDAsIDUwMCwgMTAwMCwgSW5mKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiPCAwLjVLIiwgIjAuNS0xSyIsICIxSysiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByaWdodCA9IEZBTFNFKSkgICMgUmlnaHQgPSBGQUxTRSBtZWFucyA2MDAgaXMgaW4gIjYwMC02NjAiDQoNCiMgQ3JlYXRlIGJhciBwbG90IGZvciBBbW91bnQgRHVyYXRpb24NCkFtb3VudF9wbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IEFtb3VudCwgZmlsbCA9IEFtb3VudCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJuYXZ5IikgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbnQgYnkgQW1vdW50IiwNCiAgICAgICB4ID0gIkFtb3VudCIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSByZWR1bmRhbnQgbGVnZW5kDQoNCg0KZ3JpZC5hcnJhbmdlKCBDYXJfbG9hbl9wbG90LCBQZXJzb25hbF9sb2FuX3Bsb3QsIEhvbWVfbG9hbl9wbG90LCBFZHVjYXRpb25fbG9hbl9wbG90LCBDaGVja2luZ19hbW91bnRfcGxvdCwgQ3JlZGl0X3Njb3JldF9wbG90LCBTYXZpbmdfYW1vdW50X3Bsb3QsIEFtb3VudF9wbG90LCBuY29sID0gNCkNCg0KYGBgDQoNCvCfn6YgQ2FyIExvYW5zOiBvdmVyIDYwJSBvZiBhY2NvdW50cyBoYXZlIGNhciBsb2Fucw0KDQrwn5+nIFBlcnNvbmFsIExvYW5zOiB+IDUwJSBvZiBhY2NvdW50cyBtYWludGFpbnMgcGVyc29uYWwgbG9hbnMuDQoNCvCfn6kgSG9tZSBMb2FuczogIE1vcmUgdGhhbiA5MCUgb2YgYWNjb3VudHMgaGF2ZSBob21lIGxvYW5zLg0KDQrwn5+lIEVkdWNhdGlvbiBMb2FuczogU2lnbmlmaWNhbnQgcG90aW9uIG9mIHRoZSBhY2NvdW50cyBzdGlsbCBoYXZlIGVkdWNhdGlvbiBsb2FucyB3aXRoIG91dHN0YW5kaW5nIGJhbGFuY2UgKH4gODAlKS4NCg0K8J+fpiBDaGVja2luZyBBY2NvdW50cyBCYWxhbmNlOiBNYWpvcml0eSBmIGFjY291bnRzIG1haW50YWluZWQgYmV0d2VlbiAkIDAgYW5kIDEwMDAgYmFsYW5jZSBpbiB0aGVpciBhY2NvdW50cy4NCg0K8J+fpyBDcmVkaXQgU2NvcmU6IEFsbW9zdCA4MCUgb2YgYWNjb3VudHMgaGF2ZSBjcmVkaXQgc2NvcmUgb2YgNzIwKy4NCg0K8J+fqSBTYXZpbmcgQW1vdW50OiBTYXZpbmcgYWNjb3VudHMgYmFsYW5jZSBkaXN0cmlidXRlZCBiZXR3ZWVuICQgMi0zSyAofiAzMCUpIGFuZCAkIDMtNEsgKH4gNzAlKS4NCg0K8J+fpSBBbW91bnQ6IH4gMjAlIG9mIGFjY291bnRzIGhhdmUgYW1vdW50IGJldHdlZW4gJCA1MDAtMTAwMCBhbmQgcmVtYWluaW5nIGhhdmluZyBhbW91bnQgYW1vdW5zIGdyZWF0ZXIgdGhhbiAkIDEwMDAgd2l0aG91dCBhbnkgYWNjb3VudHMgd2l0aCBhbW91bnQgZ3JlYXRlciB0aGFuICQgMjAwMC4NCg0KIyMgb3V0bGllcnMNCg0KSW5zcGVjdGluZyBjb250aW51b3VzIC8gbnVtZXJpYyBhcmFibGUgZm9yIHBvdGVudGlhbCBvdXRsaWVycw0KDQpgYGB7cn0NCiMgTG9hZCByZXF1aXJlZCBsaWJyYXJ5DQpsaWJyYXJ5KHBsb3RseSkNCg0KIyBDcmVhdGUgaW5kaXZpZHVhbCBpbnRlcmFjdGl2ZSBib3hwbG90cw0KcDEgPC0gcGxvdF9seShsZGQsIHkgPSB+Q2hlY2tpbmdfYW1vdW50LCB0eXBlID0gImJveCIsIG5hbWUgPSAiQ2hlY2tpbmcgQW1vdW50IiwgYm94cG9pbnRzID0gIm91dGxpZXJzIiwgbWFya2VyID0gbGlzdChjb2xvciA9ICJicm93bjIiKSkNCnAyIDwtIHBsb3RfbHkobGRkLCB5ID0gfkNyZWRpdF9zY29yZSwgdHlwZSA9ICJib3giLCBuYW1lID0gIkNyZWRpdCBTY29yZSIsIGJveHBvaW50cyA9ICJvdXRsaWVycyIsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicHVycGxlMSIpKQ0KcDMgPC0gcGxvdF9seShsZGQsIHkgPSB+U2F2aW5nX2Ftb3VudCwgdHlwZSA9ICJib3giLCBuYW1lID0gIlNhdmluZyBBbW91bnQiLCBib3hwb2ludHMgPSAib3V0bGllcnMiLCBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImN5YW40IikpDQpwNCA8LSBwbG90X2x5KGxkZCwgeSA9IH5BbW91bnQsIHR5cGUgPSAiYm94IiwgbmFtZSA9ICJBbW91bnQiLCBib3hwb2ludHMgPSAib3V0bGllcnMiLCBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJyb3duMiIpKQ0KDQoNCiMgQXJyYW5nZSB0aGVtIGluIGEgc2luZ2xlIHJvdyB1c2luZyBzdWJwbG90DQpzdWJwbG90KHAxLCBwMiwgcDMsIHA0LCBucm93cyA9IDEsIHNoYXJlWSA9IEZBTFNFLCB0aXRsZVggPSBUUlVFKQ0KYGBgDQoNCvCfn6YgQ2hlY2tpbmcgQW1vdW50OiBXZSBjYW4gc2VlIGZldyBvdXRsaWVycyB3aXRoIGNoZWNraW5nIGFjY291bnQgYmFsYW5jZSBiZWxvdyBuZWdhdGl2ZSA1MDAgYW5kIGZldyA+IDEwMDAgYnV0IG5vdGhpbmcgdG9vIGZhciB0byBza2V3IHRoZSBkYXRhDQoNCvCfn6cgQ3JlZGl0IFNjb3JlOiBJdCBpcyBub3RpY2VhYmxlIHRoYXQgd2UgaGF2ZSBtYW55IGFjY291bnRzIHdpdGggY3JlZGl0IHNjb3JlIGJlbG93IDYwMC4gVGhlc2UgY291bGQgaW1wYWN0IC8gaW5kaWNhdGUgdGhlIHByb2JhYmlsaXR5IG9mIGRlZmF1bHQuDQoNCvCfn6kgU2F2aW5nIEFtb3VudDogd2hpbGUgd2UgY2FuIHNlZSBmZXcgb3V0bGllcnMsIG5vdGhpbmcgc3RhbmRzIG91dC4NCg0K8J+fpSBBbW91bnQ6IFNpbWlsYXIgdG8gc2F2aW5nIGFtb3VudCwgbm90aGluZyBzdGFuZHMgb3V0DQoNCiMgUHJlZGljdGluZyBQcmJhYmlsaXR5IG9mIERlZmF1bHQgdXNpbmcgUGVyY2VwdHJvbiBDbGFzc2lmaWNhdGlvbg0KDQpXZSB1c2VkIHRoZSBwZXJjZXB0cm9uIGNsYXNzaWZpY2F0aW9uIHdpdGggYWN0aXZhdGlvbiBzaWdtb2lkIHNpbmNlIGl0IGlzIGVxdWl2YWxlbnQgdG8gdGhlIGNsYXNzaWNhbCBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlLg0KDQpUaGUgcGVyY2VwdHJvbiBjbGFzc2lmaWNhdGlvbiBidWlsZGluZyBwcm9jZXNzIGZvbGxvd3MgdGhlIGZvbGxvd2luZyBzdGVwczoNCg0KICAtIEZlYXR1cmUgc2NhbGluZyBhbmQgZGF0YSBzcGxpdHRpbmcNCiAgLSBIeXBlcnBhcmFtZXRlciB0dW5pbmcNCiAgLSBGaW5hbCBtb2RlbCB0cmFpbmluZyB3aXRoIHRoZSBvcHRpbWFsIGNvbWJpbmF0aW9uIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcw0KICAtIFByZWRpY3Rpb24gYW5kIHBlcmZvcm1hbmNlIGV2YWx1YXRpb24NCiAgLSBJbXBsZW1lbnRhdGlvbjogRGV0ZXJtaW5pbmcgdGhlIG9wdGltYWwgdGhyZXNob2xkIGJhc2VkIG9uIGFwcHJvcHJpYXRlIHBlcmZvcm1hbmNlIG1ldHJpY3MNCg0KDQojIyBGZWF0dXJlIHNjYWxpbmcgYW5kIGRhdGEgc3BsaXR0aW5nDQoNCkRhdGEgd2FzIHNwbGl0IDcwLzMwIGFmdGVyIGNvbnZlcnRpbmcgYWxsIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbnRvIG51bWVyaWMgdmFyaWFibGVzLiB0aGVuIHdlIHNjYWxlZCB0aGUgdmFyaWFibGVzIGV4Y2VwdCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgIkRlZmF1bHQiDQogIA0KYGBge3J9DQoNCmxkZCA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NhbWVyYWx6YWltL1cwMi9yZWZzL2hlYWRzL21haW4vQmFua0xvYW5EZWZhdWx0RGF0YXNldC5jc3YiKQ0KDQojIElkZW50aWZ5IGNoYXJhY3RlciAoY2F0ZWdvcmljYWwpIHZhcmlhYmxlcyBhY3Jvc3MgY29sdW1uDQpjYXRlZ29yaWNhbC52YXJzIDwtIHNhcHBseShsZGQsIGlzLmNoYXJhY3RlcikgICAjIHRlc3QgZm9yIGNoYXJhY3Rlci4uLg0KDQoNCiMgT25lLWhvdCBlbmNvZGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzOiBjcmVhdGVzIGEgZnVsbCBzZXQgb2YgZHVtbXkgdmFyaWFibGVzIA0KIyAoaS5lLiBsZXNzIHRoYW4gZnVsbCByYW5rIHBhcmFtZXRlcml6YXRpb24pDQpkdW1taWVzIDwtIGR1bW15VmFycygiIH4gLiAiLCBkYXRhID0gbGRkWywgY2F0ZWdvcmljYWwudmFyc10pDQoNCiMjIFRoZSBmb2xsb3dpbmcgcHJlZGljdCBmdW5jdGlvbiBwcm9kdWNlcyBhIGRhdGEgbWF0cml4DQpjYXRlZ29yaWNhbC5lbmNvZGVkIDwtIHByZWRpY3QoZHVtbWllcywgbmV3ZGF0YSA9IGxkZFssIGNhdGVnb3JpY2FsLnZhcnNdKQ0KY2F0ZWdvcmljYWwuZW5jb2RlZCA8LSBhcy5kYXRhLmZyYW1lKGNhdGVnb3JpY2FsLmVuY29kZWQpDQoNCm5hbWVzKGNhdGVnb3JpY2FsLmVuY29kZWQpW25hbWVzKGNhdGVnb3JpY2FsLmVuY29kZWQpID09ICJHZW5kZXJGZW1hbGUiXSA8LSAiR2VuZGVyRmVtYWxlIg0KIyByZW5hbWVzIHZhcmlhYmxlIG5hbWVzIHRvIHJlcGxhY2UgIi0iIHdpdGggIi4iDQpuYW1lcyhjYXRlZ29yaWNhbC5lbmNvZGVkKVtuYW1lcyhjYXRlZ29yaWNhbC5lbmNvZGVkKSA9PSAiR2VuZGVyTWFsZSJdIDwtICJHZW5kZXJNYWxlIg0KDQoNCiMgQ29tYmluZSB3aXRoIG51bWVyaWMgdmFyaWFibGVzICh3aGljaCBkb24ndCBuZWVkIGVuY29kaW5nKQ0KbnVtZXJpYy52YXJzIDwtIGxkZFssICFjYXRlZ29yaWNhbC52YXJzIF0NCg0KcHJvY2Vzc2VkLmRhdGEgPC0gY2JpbmQobnVtZXJpYy52YXJzLCBjYXRlZ29yaWNhbC5lbmNvZGVkKQ0KDQoNCiMgU2NhbGUgbnVtZXJpYyB2YXJpYWJsZXMgKGV4Y2x1ZGluZyB0aGUgdGFyZ2V0KQ0KbnVtZXJpYy5jb2xzIDwtIHNhcHBseShwcm9jZXNzZWQuZGF0YSwgaXMubnVtZXJpYykgJiBuYW1lcyhwcm9jZXNzZWQuZGF0YSkgIT0gIkRlZmF1bHQiDQojIyBieSBkZWZhdWx0LCBzY2FsZSgpIHRha2VzICd6LXNjb3JlJyB0cmFuc2Zvcm1hdGlvbg0KcHJvY2Vzc2VkLmRhdGFbLCBudW1lcmljLmNvbHNdIDwtIHNjYWxlKHByb2Nlc3NlZC5kYXRhWywgbnVtZXJpYy5jb2xzXSkNCg0KDQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlczogc2luY2UgbmV1cmFsbmV0KCkgZG9lcyBub3QgaGFuZGxlIG1pc3NpbmcgdmFsdWVzDQojc3VtKGlzLm5hKHByb2Nlc3NlZC5kYXRhKSkNCg0KDQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQpzZXQuc2VlZCgxMjMpDQoNCiMgc2FtcGxlIHNpemUNCm5uIDwtIGxlbmd0aChwcm9jZXNzZWQuZGF0YSREZWZhdWx0KQ0KdHJhaW4uaW5kZXguY2xzIDwtIHNhbXBsZSgxOm5uLCByb3VuZCgwLjcqbm4pKSAgICMgcmFuZG9tIG9icyBJRA0KdHJhaW4uZGF0YS5jbHMgPC0gcHJvY2Vzc2VkLmRhdGFbdHJhaW4uaW5kZXguY2xzLCBdDQp0ZXN0LmRhdGEuY2xzIDwtIHByb2Nlc3NlZC5kYXRhWy10cmFpbi5pbmRleC5jbHMsIF0NCg0KIyMNCnRyYWluLmRhdGEub3JpZyA8LSBsZGRbdHJhaW4uaW5kZXguY2xzLCBdDQp0ZXN0LmRhdGEub3JpZyA8LSBsZGRbLXRyYWluLmluZGV4LmNscywgXQ0KDQojdGFibGUodHJhaW4uZGF0YS5jbHMkRGVmYXVsdCkNCiN0YWJsZSh0ZXN0LmRhdGEuY2xzJERlZmF1bHQpDQoNCmBgYA0KDQpgYGB7cn0NCiNzdHIgKHByb2Nlc3NlZC5kYXRhKQ0KI3RhYmxlKHRyYWluLmRhdGEuY2xzJERlZmF1bHQpDQojdGFibGUodGVzdC5kYXRhLmNscyREZWZhdWx0KQ0KYGBgDQoNCiMjIEh5cGVycGFyYW1ldGVyIFR1bmluZw0KDQpIYXZlIHVzZWQgbGVhcm5pbmcgcmF0ZSBvZiAoMC4wMDEsIDAuMDEsIDAuMDUsIDAuMSwgMC4yKSB3aXRoIHN0b3BwaW5nIHRocmVzaG9sZCBvZiAoMC4xLDAuMDUpIGFmdGVyIGF0dGVtcHRpbmcgbXVsdGlwbGUgZGlmZmVybnQgbGVhcm5pbmcgcmF0ZXMgYW5kIHN0b3BwaW5nIHRocmVzaG9sZHMgaW4gb3JkZXIgdG8gaW1wcm92ZSB0aGUgbW9kZWwgYWNjdXJhY3kuDQoNCkR1cmluZyB0aGUgdHVuaW5nIHByb2Nlc3MsIHBlcmZvcm1lZCA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiB0byBvYnRhaW4gYSBzdGFibGUgYWNjdXJhY3kgc2NvcmUsIHVzaW5nIGEgZGVmYXVsdCB0aHJlc2hvbGQgb2YgMC41IChmb3IgdGhlIHNpZ21vaWQgcGVyY2VwdHJvbikNCg0KYGBge3J9DQoNCiMjIEdyaWQgU2VhcmNoIFNldHVwDQojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZA0KaHlwZXIuZ3JpZC5jbHMgPC0gZXhwYW5kLmdyaWQoDQogIGxlYXJuaW5ncmF0ZSA9IGMoMC4wMDEsIDAuMDEsIDAuMDUsIDAuMSwgMC4yKSwNCiAgdGhyZXNob2xkID0gYygwLjAxLCAwLjA1KSAgIyBTdG9wcGluZyB0aHJlc2hvbGQgZm9yIHBhcnRpYWwgZGVyaXZhdGl2ZXMNCiApDQoNCiMgQ3JlYXRlIGZvcm11bGEgZm9yIG5ldXJhbCBuZXR3b3JrDQpmb3JtdWxhIDwtIGFzLmZvcm11bGEocGFzdGUoIkRlZmF1bHQgfiIsIHBhc3RlKG5hbWVzKHRyYWluLmRhdGEuY2xzKVshbmFtZXModHJhaW4uZGF0YS5jbHMpICVpbiUgIkRlZmF1bHQiXSwgY29sbGFwc2UgPSAiICsgIikpKQ0KDQojIFNldCB1cCA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbjogY3JlYXRlRm9sZHMoKSByZXR1cm5zIGEgbGlzdCBvZiBmb2xkIG9icyBJRHMNCiMgcmV0dXJuVHJhaW4gPSBGQUxTRSA9PiBubyBwcmludCBvdXQNCiNmb2xkcyA8LSBjcmVhdGVGb2xkcyh0cmFpbi5kYXRhLmNscyR5LCBrID0gNSwgbGlzdCA9IFRSVUUsIHJldHVyblRyYWluID0gRkFMU0UpDQoNCiMjDQoNCmsgPC0gNQ0KZm9sZC5zaXplIDwtIGZsb29yKGRpbSh0cmFpbi5kYXRhLmNscylbMV0vaykNCiMgSW5pdGlhbGl6ZSByZXN1bHRzIHN0b3JhZ2UNCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgbGVhcm5pbmdyYXRlID0gbnVtZXJpYygpLA0KICB0aHJlc2hvbGQgPSBudW1lcmljKCksDQogIGFjY3VyYWN5ID0gbnVtZXJpYygpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyMgUGVyZm9ybSBHcmlkIFNlYXJjaCB3aXRoIENyb3NzLVZhbGlkYXRpb24NCmZvcihpIGluIDE6bnJvdyhoeXBlci5ncmlkLmNscykpIHsNCiAgbHIgPC0gaHlwZXIuZ3JpZC5jbHMkbGVhcm5pbmdyYXRlW2ldDQogIHRoIDwtIGh5cGVyLmdyaWQuY2xzJHRocmVzaG9sZFtpXQ0KICANCiAgZm9sZC5hY2N1cmFjaWVzIDwtIG51bWVyaWMoaykNCg0KDQojIyBQZXJmb3JtIEdyaWQgU2VhcmNoIHdpdGggQ3Jvc3MtVmFsaWRhdGlvbg0KZm9yKGZvbGQgaW4gMTprKSB7DQogICAgIyBTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMNCiAgICB2YWxpZC5pbmRpY2VzIDwtICgxICsgKGZvbGQtMSkqZm9sZC5zaXplKTooZm9sZCpmb2xkLnNpemUpDQogICAgdHJhaW4uZm9sZCA8LSB0cmFpbi5kYXRhLmNsc1stdmFsaWQuaW5kaWNlcywgXQ0KICAgIHZhbGlkLmZvbGQgPC0gdHJhaW4uZGF0YS5jbHNbdmFsaWQuaW5kaWNlcywgXQ0KfQ0KDQogICAgIyBUcmFpbiB0aGUgcGVyY2VwdHJvbg0KICAgIHNldC5zZWVkKDEyMykNCiAgIG1vZGVsLnNpZ21vaWQgPC0gbmV1cmFsbmV0KA0KICBmb3JtdWxhLA0KICBkYXRhID0gdHJhaW4uZm9sZCwNCiAgaGlkZGVuID0gMCwgICMgU3RhcnQgd2l0aCAxIGhpZGRlbiB1bml0DQogIGxpbmVhci5vdXRwdXQgPSBGQUxTRSwNCiAgbGVhcm5pbmdyYXRlID0gbHIsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiLA0KICBhbGdvcml0aG0gPSAicnByb3ArIiwgICMgUmVzaWxpZW50IEJhY2twcm9wYWdhdGlvbg0KICB0aHJlc2hvbGQgPSB0aCwNCiAgc3RlcG1heCA9IDFlNQ0KKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWRzIDwtIHByZWRpY3QobW9kZWwuc2lnbW9pZCwgdmFsaWQuZm9sZCkNCg0KI3JvYy5zaWdtb2lkIDwtIHJvYyh2YWxpZC5mb2xkJERlZmF1bHQsIHByZWRzKQ0KI2Jlc3QudGhyZXNob2xkIDwtIGNvb3Jkcyhyb2Muc2lnbW9pZCwgImJlc3QiLCByZXQgPSAidGhyZXNob2xkIikNCg0KI3ByZWRzIDwtIGFzLnZlY3RvcihwcmVkcykNCg0KI3ByZWQuY2xhc3MgPC0gaWZlbHNlKHByZWRzID4gYmVzdC50aHJlc2hvbGQsIDEsIDApDQpwcmVkLmNsYXNzZXMgPC0gaWZlbHNlKHByZWRzID4gMC40LCAxLCAwKSAgIyBkZWZhdWx0IHRocmVzaG9sZCAwLjUNCg0KI3ByaW50IChwcmVkcykNCiNwcmludCAocHJlZC5jbGFzc2VzKQ0KDQojIEFjY3VyYWN5DQpmb2xkLmFjY3VyYWNpZXNbZm9sZF0gPC0gbWVhbihwcmVkLmNsYXNzZXMgPT0gdmFsaWQuZm9sZCREZWZhdWx0KQ0KDQojcHJpbnQgKHByZWQuY2xhc3NlcykNCiNwcmludCAoZm9sZF9hY2N1cmFjeSkNCg0KDQogICMgU3RvcmUgYXZlcmFnZSBhY2N1cmFjeSBmb3IgdGhpcyBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbg0KICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoDQogICAgbGVhcm5pbmdyYXRlID0gbHIsDQogICAgdGhyZXNob2xkID0gdGgsDQogICAgYWNjdXJhY3kgPSBtZWFuKGZvbGQuYWNjdXJhY2llcykNCiAgICANCiAgKSkNCn0NCiNwcmludCAocmVzdWx0cykNCg0KIyMgQW5hbHl6ZSBSZXN1bHRzDQojIEZpbmQgdGhlIGJlc3QgY29tYmluYXRpb24NCmJlc3QuY29tYmluYXRpb24gPC0gcmVzdWx0c1t3aGljaC5tYXgocmVzdWx0cyRhY2N1cmFjeSksIF0NCg0KI2NhdCgiXG5CZXN0IGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9uOlxuIikNCnBhbmRlcihiZXN0LmNvbWJpbmF0aW9uKQ0KDQpgYGANCg0KYGBge3J9DQojc3RyIChwcmVkcykNCiNzdHIocHJlZC5jbGFzc2VzKQ0KI3RhYmxlIChwcmVkLmNsYXNzZXMpDQojdGFibGUgKHZhbGlkLmZvbGQkRGVmYXVsdCkNCiNzdHIodmFsaWQuZm9sZCREZWZhdWx0KQ0KI3RhYmxlIChmb2xkLmFjY3VyYWNpZXMpDQojIENvbnZlcnQgcHJlZHMgdG8gYSBudW1lcmljIHZlY3RvciAoaWYgbmVlZGVkKQ0KI3ByZWRzIDwtIGFzLnZlY3RvcihwcmVkcykNCg0KYGBgDQoNCmBgYHtyfQ0KI3RhYmxlKHRyYWluLmRhdGEuY2xzJERlZmF1bHQpDQojcHJvcC50YWJsZSh0YWJsZSh0cmFpbi5kYXRhLmNscyREZWZhdWx0KSkNCiNzdW1tYXJ5KHByZWRzKQ0KaGlzdChwcmVkcywgbWFpbj0iTmV1cmFsIE5ldHdvcmsgUHJlZGljdGlvbiBEaXN0cmlidXRpb24iLCBjb2w9InNreWJsdWUiKQ0KYGBgDQoNClRoZSBhYm92ZSBpZGVudGlmaWVkIOKAmG9wdGltYWzigJkgY29tYmluYXRpb24gb2YgaHlwZXJwYXJhbWV0ZXJzLCBzaG93ZWQgdGhhdCBtb2RlbCBhY2N1cmFjeSBpcyBsb3cuIHRoaXMgZHJpdmVuIG1haW5seSBieSBpbWJhbGFuY2UgaW4gdGhlIHJlc3BvbnNlIHZhcmlhYmxlICgzMCUgZGVmYXVsdCkuIFdlIHdpbGwgY29udGludWUgd2l0aCB0cmFpbmluZyB0aGUgZmluYWwgcGVyY2VwdHJvbiBtb2RlbCB0byBzZWUgdGhlIG1vZGVsIHNlcGFyYXRpb24gcG93ZXIuDQoNCiMjIFRyYWluaW5nIEZpbmFsIE1vZGVsDQoNCkhhdmUgdXNlZCBuZXVyYWxuZXQoKSB0byB0cmFpbiB0aGUgZmluYWwgcGVyY2VwdHJvbiBtb2RlbCB3aXRoIHRoZSB0dW5lZCBoeXBlcnBhcmFtZXRlcnMgYW5kIHRoZSBmb2xsb3dpbmcgYXNzdW1wdGlvbnM6DQoNCiAgLSBNb2RlbCBmb3JtdWxhOiBUaGUgZm9ybXVsYSBtdXN0IGluY2x1ZGUgc2NhbGVkIG51bWVyaWNhbCBmZWF0dXJlIHZhcmlhYmxlcy4NCiAgLSBoaWRkZW4gPSAwOiBUaGlzIGVuc3VyZXMgdGhlIG1vZGVsIGlzIGEgcGVyY2VwdHJvbiAobm8gaGlkZGVuIGxheWVycykuDQogIC0gbGluZWFyLm91dHB1dCA9IEZBTFNFOiBSZXF1aXJlZCBmb3IgY2xhc3NpZmljYXRpb24gdGFza3MuDQogIC0gVGhlIGFjdGl2YXRpb24gZnVuY3Rpb24gdXNlZCB3YXMgbG9naXN0aWMgLg0KICAtIGFsZ29yaXRobSA9OiAocnByb3ApIHVzZWQgYnV0IHVzZWQgcmVzaWxpZW50IHJwcm9wKyBmb3IgdHJhaW5pbmcgdGhlIGZpbmFsIG1vZGVsDQoNCmBgYHtyfQ0KIyMgVHJhaW4gRmluYWwgTW9kZWwgd2l0aCBCZXN0IEh5cGVycGFyYW1ldGVycw0KZmluYWwuc2lnbW9pZC5tb2RlbCA8LSBuZXVyYWxuZXQoDQogIGZvcm11bGEsDQogIGRhdGEgPSB0cmFpbi5kYXRhLmNscywNCiAgaGlkZGVuID0gMCwNCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBsZWFybmluZ3JhdGUgPSBiZXN0LmNvbWJpbmF0aW9uJGxlYXJuaW5ncmF0ZSwNCiAgdGhyZXNob2xkID0gYmVzdC5jb21iaW5hdGlvbiR0aHJlc2hvbGQsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiLA0KICBhbGdvcml0aG0gPSAicnByb3ArIiwgIyBUaGUgcmVzaWxpZW50IGJhY2twcm9wYWdhdGlvbiB3aXRoIHdlaWdodCBiYWNrdHJhY2tpbmcNCiAgc3RlcG1heCA9IDFlNQ0KKQ0KIyBQbG90IHRoZSBmaW5hbCBtb2RlbA0KIHBsb3QoZmluYWwuc2lnbW9pZC5tb2RlbCkNCg0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjcwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMvZmFyZXMvT25lRHJpdmUvRGVza3RvcC9TVEE1NTIvUHJvamVjdCA0L1BpY3R1cmUxLmpwZyIpDQpgYGANCg0KVGhlIG1vZGVsIHVzZWQgMTggc2NhbGVkIG51bWVyaWNhbCB2YXJpYWJsZXMgd2l0aCBkaWZmZXJlbnQgcG93ZXIuIA0KDQpgYGB7cn0NCiNzdHIodGVzdC5kYXRhLmNscykgDQpgYGANCg0KIyMgUk9DIEFuYWx5c2lzIGFuZCBDb21wYXJpc29uDQoNCmBgYHtyfQ0KDQojIw0KIyMgRXZhbHVhdGUgb24gVGVzdCBTZXQNCnByZWQuc2lnbW9pZCA8LSBwcmVkaWN0KGZpbmFsLnNpZ21vaWQubW9kZWwsIHRlc3QuZGF0YS5jbHMpDQoNCiMjIyAgbG9naXN0aWMgcmVncmVzc2lvbg0KbG9naXQuZml0IDwtIGdsbShEZWZhdWx0IH4gLiwgZGF0YSA9IHRyYWluLmRhdGEuY2xzLCBmYW1pbHkgPSBiaW5vbWlhbCkNCkFJQy5sb2dpdCA8LSBzdGVwKGxvZ2l0LmZpdCwgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IDApDQpwcmVkLmxvZ2l0IDwtIHByZWRpY3QoQUlDLmxvZ2l0LCB0ZXN0LmRhdGEuY2xzLCB0eXBlID0gInJlc3BvbnNlIikNCnByZWQuZnVsbCA8LSBwcmVkaWN0KGxvZ2l0LmZpdCwgdGVzdC5kYXRhLmNscywgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMjIHJvYw0KDQpyb2MuZnVsbC5sb2dpdCA8LSByb2ModGVzdC5kYXRhLmNscyREZWZhdWx0LCBwcmVkLmZ1bGwpDQpyb2MuQUlDLmxvZ2l0IDwtIHJvYyh0ZXN0LmRhdGEuY2xzJERlZmF1bHQsIHByZWQubG9naXQpDQpyb2Muc2lnbW9pZCA8LSByb2ModGVzdC5kYXRhLmNscyREZWZhdWx0LCBwcmVkLnNpZ21vaWQgKQ0KDQojIyBBVUMNCmF1Yy5zaWdtb2lkIDwtIHJvYy5zaWdtb2lkJGF1Yw0KYXVjLmZ1bGwubG9naXQgPC0gcm9jLmZ1bGwubG9naXQkYXVjDQphdWMuQUlDLmxvZ2l0IDwtIHJvYy5BSUMubG9naXQkYXVjDQoNCiMjIHNwZS1zZW4NCnNpZ21vaWQuc3BlIDwtIHJvYy5zaWdtb2lkJHNwZWNpZmljaXRpZXMNCnNpZ21vaWQuc2VuIDwtIHJvYy5zaWdtb2lkJHNlbnNpdGl2aXRpZXMNCg0KZnVsbC5sb2dpdC5zcGUgPC0gcm9jLmZ1bGwubG9naXQkc3BlY2lmaWNpdGllcw0KZnVsbC5sb2dpdC5zZW4gPC0gcm9jLmZ1bGwubG9naXQkc2Vuc2l0aXZpdGllcw0KDQpBSUMubG9naXQuc3BlIDwtIHJvYy5BSUMubG9naXQkc3BlY2lmaWNpdGllcw0KQUlDLmxvZ2l0LnNlbiA8LSByb2MuQUlDLmxvZ2l0JHNlbnNpdGl2aXRpZXMNCg0KIyBST0MgY3VydmUNCnBsb3QoMS1zaWdtb2lkLnNwZSwgc2lnbW9pZC5zZW4sIGNvbCA9ICJuYXZ5IiwgdHlwZSA9ICJsIiwgbHR5ID0gMSwNCiAgICAgeGxhYiA9ICIxIC0gc3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gInNlbnNpdGl2aXR5IiwNCiAgICAgbWFpbiA9ICJST0MgQ3VydmVzIG9mIFBlcmNlcHRyb24gYW5kIExvZ2lzdGljIE1vZGVscyIpDQpsaW5lcygxLWZ1bGwubG9naXQuc3BlLCBmdWxsLmxvZ2l0LnNlbiwgbHR5ID0gMSwgY29sID0gImN5YW40IikNCmxpbmVzKDEtQUlDLmxvZ2l0LnNwZSwgQUlDLmxvZ2l0LnNlbiwgbHR5ID0gMSwgY29sID0gImN5YW4iKQ0KYWJsaW5lKDAsMSwgbHR5ID0yLCBjb2wgPSAicmVkIikNCnRleHQoMC45OCwgMC4zLCBwYXN0ZSgiUGVyY2VwdHJvbiBBVUMgPSAiLCByb3VuZChhdWMuc2lnbW9pZCw0KSksIGNvbCA9ICJuYXZ5IiwgY2V4ID0gMC44LCBwb3MgPSAyKQ0KdGV4dCgwLjk4LCAwLjI1LCBwYXN0ZSgiRnVsbCBMb2dpdCBBVUMgPSAiLCByb3VuZChhdWMuZnVsbC5sb2dpdCw0KSksIGNvbCA9ICJjeWFuNCIsIGNleCA9IDAuOCwgcG9zID0gMikNCnRleHQoMC45OCwgMC4yLCBwYXN0ZSgiQUlDIEFVQyA9ICIsIHJvdW5kKGF1Yy5BSUMubG9naXQsNCkpLCBjb2wgPSAiY3lhbiIsIGNleCA9IDAuOCwgcG9zID0gMikNCg0KYGBgDQoNClRoZSBST0MgY3VydmVzIGFib3ZlIHNob3cgdGhhdCB0aGUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscyBwZXJmb3JtIHNpbWlsYXJseS4gQWxsIDMgbW9kZWxzIHNob3dpbmcgQVVDIGhpZ2hlciB0aGFuIDAuOTggdGhvdWdoIHRoZSBhY2N1cmFjeSA8IDAuMi4NCg0KIyMgUGVyY2VwdHJvbiBDbGFzaWZpY2F0aW9uIENvbmNsdWRpbmcgUmVtYXJrcw0KDQpQZXJjZXB0cm9uIGNsYXNzaWZpY2F0aW9uIHdpdGggYW4gaWRlbnRpdHkgYWN0aXZhdGlvbiBmdW5jdGlvbiwgdGhlIG1vZGVsIGlzIGVxdWl2YWxlbnQgb3Igc2xpZ2h0bHkgaW5mZXJpb3IgdG8gbGluZWFyIHJlZ3Jlc3Npb24NCg0KQ29uc2lkZXJpbmcgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBhYm92ZSBtb2RlbCwgd2hlcmUgd2UgYXJlIHNlZWluZyBtaXNtYXRjaCBiZXR3ZWVuIGFjY3VyYWN5IGFuZCBBVUMsIHdlIHdpbGwgdHJ5IHByZWRpY3RpbmcgdGhlIGRlZmF1bHQgdXRpbGl6aW5nIG11bHRpbGF5ZXIgTmV1cmFsIE5ldHdvcmsgdG8gc2VlIGlmIHdlIGNhbiBnZXQgYmV0dGVyIC8gbW9yZSBjb25zaXN0ZW50IHByZWRpY3Rpb24NCg0KIyBNdWxpbGF5ZXIgTmV1cmFsIE5ldHdvcmsNCg0KIyMgU2NhbGxpbmcgdGhlIGRhdGENCg0KdXNpbmcgZGlmZmVyZW50IHNjYWxpbmcgLyBub3JtYWxpemF0aW9uIHRvIGNvbXBhcmUgYm90aCByZXN1bHRzLg0KDQoNCmBgYHtyfQ0KDQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KIyBsaWJyYXJ5KG5ldXJhbG5ldCkNCiMgbGlicmFyeShwUk9DKSAgICAgIyBGb3IgUk9DIGFuYWx5c2lzDQojIGxpYnJhcnkoZ2dwbG90MikgICMgRm9yIHZpc3VhbGl6YXRpb24NCg0KIyMgTm8gbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGRhdGFzZXQuDQoNCiMgRmVhdHVyZSBzY2FsaW5nIC0gbm9ybWFsaXplIG51bWVyaWMgdmFyaWFibGVzIHRvIFswLDFdIHJhbmdlDQpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCkgew0KICByZXR1cm4gKCh4IC0gbWluKHgpKSAvIChtYXgoeCkgLSBtaW4oeCkpKQ0KfQ0KDQojIEFwcGx5IG5vcm1hbGl6YXRpb24gdG8gYWxsIG51bWVyaWMgY29sdW1ucw0KbnVtZXJpYy5jb2xzIDwtIHNhcHBseShwcm9jZXNzZWQuZGF0YSwgaXMubnVtZXJpYykNCnByb2Nlc3NlZC5kYXRhW251bWVyaWMuY29sc10gPC0gbGFwcGx5KHByb2Nlc3NlZC5kYXRhW251bWVyaWMuY29sc10sIG5vcm1hbGl6ZSkNCg0KIyBUd28td2F5IGRhdGEgc3BsaXR0aW5nOiA3MC0zMCUNCnNldC5zZWVkKDEyMykgICMgRm9yIHJlcHJvZHVjaWJpbGl0eQ0Kc2FtcGxlLnNpemUuY2xzIDwtIGZsb29yKDAuNzAgKiBucm93KHByb2Nlc3NlZC5kYXRhKSkNCnRyYWluLmluZGljZXMuY2xzIDwtIHNhbXBsZSgxOnNhbXBsZS5zaXplLmNscywgc2l6ZSA9IHNhbXBsZS5zaXplLmNscywgcmVwbGFjZSA9IEZBTFNFKQ0KDQp0cmFpbi5kYXRhLmNscyA8LSBwcm9jZXNzZWQuZGF0YVt0cmFpbi5pbmRpY2VzLmNscywgXQ0KdGVzdC5kYXRhLmNscyA8LSBwcm9jZXNzZWQuZGF0YVstdHJhaW4uaW5kaWNlcy5jbHMsIF0NCg0KYGBgDQoNClRvIHNpbXBsaWZ5IGh5cGVycGFyYW1ldGVyIHR1bmluZyBhbmQgZmluYWwgbW9kZWwgdHJhaW5pbmcgd2l0aCB0aGUgcHJlLXNlbGVjdGVkIE1MUCBhcmNoaXRlY3R1cmUgZm9yIGNsYXNzaWZpY2F0aW9uLCB3ZSBkZWZpbmUgYSBjdXN0b20gZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG51bWJlciBvZiBub2RlcyBmb3IgYm90aCBzaW5nbGUtaGlkZGVuLWxheWVyIGFuZCBkb3VibGUtaGlkZGVuLWxheWVyIE1MUHMNCg0KYGBge3J9DQoNCiMgRnVuY3Rpb24gdG8gcGVyZm9ybSBncmlkIHNlYXJjaCBmb3IgbmV1cmFsbmV0DQpuZXVyYWxuZXQuZ3JpZC5zZWFyY2ggPC0gZnVuY3Rpb24odHJhaW4uZGF0YSwgdGVzdC5kYXRhLCBoaWRkZW4ubGF5ZXJzID0gMSkgew0KICAjIERlZmluZSB0aGUgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlcnMNCiAgaWYgKGhpZGRlbi5sYXllcnMgPT0gMSkgew0KICAgIGhpZGRlbi5ub2RlcyA8LSBjKDIsIDQsIDYsIDgsIDEwKQ0KICAgIGdyaWQgPC0gZXhwYW5kLmdyaWQoaGlkZGVuID0gaGlkZGVuLm5vZGVzKQ0KICB9IGVsc2Ugew0KICAgIGhpZGRlbi5ub2RlcyA8LSBjKDIsIDQsIDYpDQogICAgZ3JpZCA8LSBleHBhbmQuZ3JpZChoaWRkZW4xID0gaGlkZGVuLm5vZGVzLCBoaWRkZW4yID0gaGlkZGVuLm5vZGVzKQ0KICB9DQogIA0KICAjIEFkZCBjb2x1bW5zIHRvIHN0b3JlIHJlc3VsdHMNCiAgZ3JpZCRhY2N1cmFjeSA8LSBOQQ0KICBncmlkJGF1YyA8LSBOQQ0KICANCiAgIyBGb3JtdWxhIGZvciBuZXVyYWwgbmV0d29yaw0KICBubi5mb3JtdWxhIDwtIGFzLmZvcm11bGEocGFzdGUoIkRlZmF1bHQgfiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShuYW1lcyh0cmFpbi5kYXRhKVshbmFtZXModHJhaW4uZGF0YSkgJWluJSAiRGVmYXVsdCJdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiICsgIikpKQ0KICANCiAgIyBQZXJmb3JtIGdyaWQgc2VhcmNoDQogIGZvciAoaSBpbiAxOm5yb3coZ3JpZCkpIHsNCiAgICBpZiAoaGlkZGVuLmxheWVycyA9PSAxKSB7DQogICAgICBoaWRkZW4gPC0gYyhncmlkJGhpZGRlbltpXSkNCiAgICB9IGVsc2Ugew0KICAgICAgaGlkZGVuIDwtIGMoZ3JpZCRoaWRkZW4xW2ldLCBncmlkJGhpZGRlbjJbaV0pDQogICAgfQ0KICAgIA0KICAgICMgVHJhaW4gdGhlIG1vZGVsDQogICAgbm4ubW9kZWwgPC0gbmV1cmFsbmV0KA0KICAgICAgZm9ybXVsYSA9IG5uLmZvcm11bGEsDQogICAgICBkYXRhID0gdHJhaW4uZGF0YSwNCiAgICAgIGhpZGRlbiA9IGhpZGRlbiwNCiAgICAgIGxpbmVhci5vdXRwdXQgPSBGQUxTRSwgICMgRm9yIGNsYXNzaWZpY2F0aW9uDQogICAgICBhY3QuZmN0ID0gImxvZ2lzdGljIiwgICAjIFNpZ21vaWQgYWN0aXZhdGlvbg0KICAgICAgc3RlcG1heCA9IDFlNiAgICAgICAgICAgIyBJbmNyZWFzZSBtYXggc3RlcHMgZm9yIGNvbnZlcmdlbmNlDQogICAgKQ0KICAgIA0KICAgICMgTWFrZSBwcmVkaWN0aW9ucw0KICAgIHByZWRpY3Rpb25zIDwtIHByZWRpY3Qobm4ubW9kZWwsIHRlc3QuZGF0YSkNCiAgICBwcmVkaWN0ZWQuY2xhc3NlcyA8LSBpZmVsc2UocHJlZGljdGlvbnMgPiAwLjUsIDEsIDApDQogICAgDQogICAgIyBDYWxjdWxhdGUgYWNjdXJhY3kNCiAgICBhY2N1cmFjeSA8LSBtZWFuKHByZWRpY3RlZC5jbGFzc2VzID09IHRlc3QuZGF0YSREZWZhdWx0KQ0KICAgIA0KICAgICMgQ2FsY3VsYXRlIEFVQw0KICAgIHJvYy5vYmogPC0gcm9jKHRlc3QuZGF0YSREZWZhdWx0LCBwcmVkaWN0aW9ucykNCiAgICBhdWMudmFsdWUgPC0gYXVjKHJvYy5vYmopDQogICAgDQogICAgIyBTdG9yZSByZXN1bHRzDQogICAgZ3JpZCRhY2N1cmFjeVtpXSA8LSBhY2N1cmFjeQ0KICAgIGdyaWQkYXVjW2ldIDwtIGF1Yy52YWx1ZQ0KICB9DQogIHJldHVybihncmlkKQ0KfQ0KYGBgDQoNCiMjIEFjY3VyYWN5IGFuZCBBVUMgd2l0aCBPbmUgSGlkZGVuIExheWVyDQoNCndlIHBlcmZvcm0gb25lLWxheWVyIGFuZCBjaGVjayB0aGUgQVVEIGFuZCBhY2N1cmFjeSB2YWx1ZXMgZm9yIGVhY2ggbnVtYmVyIG9mIG5vZHMuDQoNCmBgYHtyfQ0KIyBQZXJmb3JtIGdyaWQgc2VhcmNoIGZvciBzaW5nbGUgaGlkZGVuIGxheWVyDQpncmlkLnJlc3VsdHMuMWxheWVyIDwtIG5ldXJhbG5ldC5ncmlkLnNlYXJjaCh0cmFpbi5kYXRhPXRyYWluLmRhdGEuY2xzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QuZGF0YT10ZXN0LmRhdGEuY2xzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbi5sYXllcnMgPSAxKQ0KcGFuZGVyKGdyaWQucmVzdWx0cy4xbGF5ZXIpDQoNCmBgYA0KDQpUaGUgb3B0aW1hbCBudW1iZXIgb2Ygbm9kZXMgaW4gdGhlIGhpZGRlbiBsYXllciBpcyB0aGUgY29ycmVzcG9uZHMgdG8gdGhlIHNtYWxsZXN0IEFVQy4gU2ltaWxhcmx5LCB0aGUgcGVyZm9ybWFuY2UgdGFibGUgb2YgdHdvLWhpZGRlbi1sYXllciBNTFAgaXMgZ2l2ZW4gYmVsb3cuDQoNCiMjIEFjY3VyYWN5IGFuZCBBVUMgd2l0aCBUd28gSGlkZW4gTGF5ZXINCg0KV2UgcmVwbGljYXRlIHRoZSBhYm92ZSBzdGVwIGJ1dCB3aXRoIDIgaGlkZGVuIGxheWVycw0KDQpgYGB7cn0NCg0KIyBQZXJmb3JtIGdyaWQgc2VhcmNoIGZvciB0d28gaGlkZGVuIGxheWVycw0KZ3JpZC5yZXN1bHRzLjJsYXllciA8LSBuZXVyYWxuZXQuZ3JpZC5zZWFyY2godHJhaW4uZGF0YT10cmFpbi5kYXRhLmNscywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0LmRhdGE9dGVzdC5kYXRhLmNscywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4ubGF5ZXJzID0gMikNCnBhbmRlcihncmlkLnJlc3VsdHMuMmxheWVyKQ0KDQpgYGANCg0KIyMgT25lLWhpZGRlbi1sYXllciBNTFANCg0KV2UgdXNlIHRoZSBvcHRpbWFsIG51bWJlciBvZiBub2RlcyB0byBmaXQgdGhlIG9uZS1oaWRkZW4tbGF5ZXIgTUxQIHRvIHRoZSBkYXRhDQoNCg0KYGBge3J9DQoNCiMgRm9ybXVsYSBmb3IgbmV1cmFsIG5ldHdvcmsNCm5uLmZvcm11bGEgPC0gYXMuZm9ybXVsYShwYXN0ZSgiRGVmYXVsdCB+IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShuYW1lcyh0cmFpbi5kYXRhLmNscylbIW5hbWVzKHRyYWluLmRhdGEuY2xzKSAlaW4lICJEZWZhdWx0Il0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiICsgIikpKQ0KDQojIFRyYWluIHNpbmdsZSBoaWRkZW4gbGF5ZXIgbW9kZWwgKHVzaW5nIGJlc3QgY29uZmlndXJhdGlvbiBmcm9tIGdyaWQgc2VhcmNoKQ0KYmVzdC4xbGF5ZXIgPC0gZ3JpZC5yZXN1bHRzLjFsYXllclt3aGljaC5tYXgoZ3JpZC5yZXN1bHRzLjFsYXllciRhdWMpLCBdDQoNCm5uLjFsYXllciA8LSBuZXVyYWxuZXQoDQogIGZvcm11bGEgPSBubi5mb3JtdWxhLA0KICBkYXRhID0gdHJhaW4uZGF0YS5jbHMsDQogIGhpZGRlbiA9IGJlc3QuMWxheWVyJGhpZGRlbiwNCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIiwNCiAgc3RlcG1heCA9IDFlNg0KKQ0KIyMNCg0KICBwbG90KG5uLjFsYXllciwgbWFpbiA9IHBhc3RlKCJPbmUtaGlkZGVuLWxheWVyIHdpdGgiLCBiZXN0LjFsYXllciRoaWRkZW4sICJOb2RlcyIpKQ0KDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iNzAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9mYXJlcy9PbmVEcml2ZS9EZXNrdG9wL1NUQTU1Mi9Qcm9qZWN0IDQvUGljdHVyZTIucG5nIikNCmBgYA0KDQojIyBUd28taGlkZGVuLWxheWVyIE1MUA0KDQpXZSB1c2UgdGhlIG9wdGltYWwgbnVtYmVyIG9mIG5vZGVzIHRvIGZpdCB0aGUgVHdvLUhpZGRlbi1sYXllciBNTFAgdG8gdGhlIGRhdGEgDQoNCmBgYCB7Un0NCg0KIyBUcmFpbiB0d28gaGlkZGVuIGxheWVycyBtb2RlbCAodXNpbmcgYmVzdCBjb25maWd1cmF0aW9uIGZyb20gZ3JpZCBzZWFyY2gpDQpiZXN0LjJsYXllciA8LSBncmlkLnJlc3VsdHMuMmxheWVyW3doaWNoLm1heChncmlkLnJlc3VsdHMuMmxheWVyJGF1YyksIF0NCg0Kbm4uMmxheWVyIDwtIG5ldXJhbG5ldCgNCiAgZm9ybXVsYSA9IG5uLmZvcm11bGEsDQogIGRhdGEgPSB0cmFpbi5kYXRhLmNscywNCiAgaGlkZGVuID0gYyhiZXN0LjJsYXllciRoaWRkZW4xLCBiZXN0LjJsYXllciRoaWRkZW4yKSwNCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIiwNCiAgc3RlcG1heCA9IDFlNg0KKQ0KIyMNCnBsb3Qobm4uMmxheWVyKQ0KDQpgYGANCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI3MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL2ZhcmVzL09uZURyaXZlL0Rlc2t0b3AvU1RBNTUyL1Byb2plY3QgNC9QaWN0dXJlMy5wbmciKQ0KYGBgDQoNCkluIHRoZSB0d28gbW9kZWwgcGxvdHMgYWJvdmUsIHRoZSBFcnJvciBhbmQgU3RlcHMgdmFsdWVzIGRpc3BsYXllZCBhdCB0aGUgYm90dG9tIHJlcHJlc2VudDoNCg0KU3RlcHM6IFRoZSBudW1iZXIgb2YgdHJhaW5pbmcgaXRlcmF0aW9ucyAoZXBvY2hzKSBjb21wbGV0ZWQgZHVyaW5nIG1vZGVsIG9wdGltaXphdGlvbi4gRWFjaCBzdGVwIGNvcnJlc3BvbmRzIHRvIG9uZSBjb21wbGV0ZSBmb3J3YXJkL2JhY2t3YXJkIHBhc3MgYW5kIHdlaWdodCB1cGRhdGUgY3ljbGUuDQoNCkVycm9yczogVGhlIHRyYWluaW5nIGVycm9yIHJlZmxlY3RzIHRoZSBsb3NzIGZ1bmN0aW9uIHZhbHVlICh0eXBpY2FsbHkgU1NFIGZvciByZWdyZXNzaW9uIG9yIGNyb3NzLWVudHJvcHkgZm9yIGNsYXNzaWZpY2F0aW9uKS4gVGhlIGRpc3BsYXllZCBFcnJvciByZXByZXNlbnRzIHRoZSBmaW5hbCBlcnJvciB2YWx1ZSBhY2hpZXZlZCB3aGVuIHRoZSBvcHRpbWl6YXRpb24gcHJvY2VzcyBjb252ZXJnZXMuDQoNCk5leHQsIHdlIHdyaXRlIGEgY3VzdG9tIGZ1bmN0aW9uIHRvIGV4dHJhY3QgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgdG8gYXNzZXNzIHRoZSBnbG9iYWwgcGVyZm9ybWFuY2UgdGhyb3VnaCBST0MgY3VydmVzIGFuZCB0aGUgY29ycmVzcG9uZGluZyBhcmVhcyB1bmRlciB0aGUgUk9DIGN1cnZlcy4NCg0KDQpgYGB7cn0NCg0KIyBGdW5jdGlvbiB0byBldmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZQ0KZXZhbHVhdGUubW9kZWwgPC0gZnVuY3Rpb24obW9kZWwsIHRlc3QuZGF0YSkgew0KICAjIE1ha2UgcHJlZGljdGlvbnMNCiAgcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbCwgdGVzdC5kYXRhKQ0KICBwcmVkaWN0ZWQuY2xhc3NlcyA8LSBpZmVsc2UocHJlZGljdGlvbnMgPiAwLjUsIDEsIDApDQogIA0KICAjIENhbGN1bGF0ZSBtZXRyaWNzDQogIGFjY3VyYWN5IDwtIG1lYW4ocHJlZGljdGVkLmNsYXNzZXMgPT0gdGVzdC5kYXRhJERlZmF1bHQpDQogIGNvbmZ1c2lvbi5tYXRyaXggPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZGljdGVkLmNsYXNzZXMsIEFjdHVhbCA9IHRlc3QuZGF0YSREZWZhdWx0KQ0KICByb2Mub2JqIDwtIHJvYyh0ZXN0LmRhdGEkRGVmYXVsdCwgcHJlZGljdGlvbnMpDQogIGF1Yy52YWx1ZSA8LSBhdWMocm9jLm9iaikNCiAgDQogIHJldHVybihsaXN0KA0KICAgIGFjY3VyYWN5ID0gYWNjdXJhY3ksDQogICAgY29uZnVzaW9uLm1hdHJpeCA9IGNvbmZ1c2lvbi5tYXRyaXgsDQogICAgcm9jLm9iaiA9IHJvYy5vYmosDQogICAgYXVjID0gYXVjLnZhbHVlDQogICkpDQp9DQoNCiMgRXZhbHVhdGUgc2luZ2xlIGhpZGRlbiBsYXllciBtb2RlbA0KcGVyZi4xbGF5ZXIgPC0gZXZhbHVhdGUubW9kZWwobm4uMWxheWVyLCB0ZXN0LmRhdGEuY2xzKQ0KI3ByaW50KHBlcmYuMWxheWVyW2MoImFjY3VyYWN5IiwgImNvbmZ1c2lvbl9tYXRyaXgiLCAiYXVjIildKQ0KDQojIEV2YWx1YXRlIHR3byBoaWRkZW4gbGF5ZXJzIG1vZGVsDQpwZXJmLjJsYXllciA8LSBldmFsdWF0ZS5tb2RlbChubi4ybGF5ZXIsIHRlc3QuZGF0YS5jbHMpDQojcHJpbnQocGVyZi4ybGF5ZXJbYygiYWNjdXJhY3kiLCAiY29uZnVzaW9uX21hdHJpeCIsICJhdWMiKV0pDQoNCmBgYA0KDQpXZSB1c2UgY2xhc3NpYyBsb2dpc3RpYyByZWdyZXNzaW9uIGFzIHRoZSBiYXNlIG1vZGVsIGFuZCBjb21wYXJlIGl0IHdpdGggdGhlIHR3byBNTFBzIHVzaW5nIFJPQyBjdXJ2ZXMgYW5kIHRoZWlyIGNvcnJlc3BvbmRpbmcgQVVDIHZhbHVlcyBpbiB0aGUgZm9sbG93aW5nIGZpZ3VyZS4NCg0KYGBge3J9DQoNCiMgVHJhaW4gbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCAoYmFzZSBtb2RlbCkNCmxvZ2l0Lm1vZGVsIDwtIGdsbShEZWZhdWx0IH4gLiwgZGF0YSA9IHRyYWluLmRhdGEuY2xzLCBmYW1pbHkgPSBiaW5vbWlhbCkNCg0KIyBFdmFsdWF0ZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQpsb2dpdC5wcmVkIDwtIHByZWRpY3QobG9naXQubW9kZWwsIHRlc3QuZGF0YS5jbHMsIHR5cGUgPSAicmVzcG9uc2UiKQ0KbG9naXQuY2xhc3NlcyA8LSBpZmVsc2UobG9naXQucHJlZCA+IDAuNSwgMSwgMCkNCmxvZ2l0LmFjY3VyYWN5IDwtIG1lYW4obG9naXQuY2xhc3NlcyA9PSB0ZXN0LmRhdGEuY2xzJERlZmF1bHQpDQpsb2dpdC5yb2MgPC0gcm9jKHRlc3QuZGF0YS5jbHMkRGVmYXVsdCwgbG9naXQucHJlZCkNCmxvZ2l0LmF1YyA8LSBhdWMobG9naXQucm9jKQ0KDQojIw0Kcm9jLjFsYXllciA8LSBwZXJmLjFsYXllciRyb2Mub2JqDQpyb2MuMmxheWVyIDwtIHBlcmYuMmxheWVyJHJvYy5vYmoNCnJvYy5sb2dpdCA8LSBsb2dpdC5yb2MNCg0KIyMgc3BlY2lmaWNpdHkgYW5kIHNlbnNpdGl2aXR5DQpzZW4uMWxheWVyIDwtIHJvYy4xbGF5ZXIkc2Vuc2l0aXZpdGllcw0Kc3BlLjFsYXllciA8LSByb2MuMWxheWVyJHNwZWNpZmljaXRpZXMNCnNlbi4ybGF5ZXIgPC0gcm9jLjJsYXllciRzZW5zaXRpdml0aWVzDQpzcGUuMmxheWVyIDwtIHJvYy4ybGF5ZXIkc3BlY2lmaWNpdGllcw0Kc2VuLmxvZ2l0IDwtIHJvYy5sb2dpdCRzZW5zaXRpdml0aWVzDQpzcGUubG9naXQgPC0gcm9jLmxvZ2l0JHNwZWNpZmljaXRpZXMNCg0KIyMgQVVDDQphdWMuMWxheWVyIDwtIHJvYy4xbGF5ZXIkYXVjDQphdWMuMmxheWVyIDwtIHJvYy4ybGF5ZXIkYXVjDQphdWMubG9naXQgPC0gcm9jLmxvZ2l0JGF1Yw0KDQojIyBQbG90IFJPQyBjdXJ2ZXMgZm9yIGNvbXBhcmlzb24NCnBhcihwdHkgPSAicyIpICAgIyBtYWtlIGEgc3F1YXJlIHBsb3QgdG8gYXZhb2lkIGRpc3RvcnRpb24NCnBsb3QoMS1zcGUuMWxheWVyLCBzZW4uMWxheWVyLCB0eXBlID0gImwiLCBsdHkgPSAxLA0KICAgICBjb2wgPSAiYmx1ZSIsIA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0dml0eSIsDQogICAgIG1haW4gPSAiUk9DIEN1cnZlIENvbXBhcmlzb24iKQ0KDQpsaW5lcygxLXNwZS4ybGF5ZXIsIHNlbi4ybGF5ZXIsIGx0eSA9IDEsIGNvbCA9ICJkYXJrcmVkIikNCmxpbmVzKDEtc3BlLmxvZ2l0LCBzZW4ubG9naXQsIGx0eSA9IDEsIGNvbCA9ICJkYXJrZ3JlZW4iKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIA0KICAgICAgIGxlZ2VuZCA9IGMocGFzdGUoIjEtbGF5ZXIgTUxQIChBVUMgPSIsIHJvdW5kKHBlcmYuMWxheWVyJGF1YywgMyksICIpIiksDQogICAgICAgICAgICAgICAgICBwYXN0ZSgiMi1sYXllciBNTFAgKEFVQyA9Iiwgcm91bmQocGVyZi4ybGF5ZXIkYXVjLCAzKSwgIikiKSwNCiAgICAgICAgICAgICAgICAgIHBhc3RlKCJMb2dpc3RpYyBSZWcgKEFVQyA9Iiwgcm91bmQobG9naXQuYXVjLCAzKSwgIikiKSksDQogICAgICAgICAgICAgICAgY29sID0gYygiYmx1ZSIsICJkYXJrcmVkIiwgImRhcmtncmVlbiIpLCANCiAgICAgICAgICAgICAgICBsdHkgPSAxLCBjZXggPSAwLjcsIGJ0eSA9ICJuIikNCg0KYGBgDQoNCiMgQ29uY2x1c2lvbiANCg0KV2hlbiBjb21wYXJpbmcgdGhlIGRpZmZlcmVudCBtZXRob2RvbG9naWVzIHdlIGNhbiBzZWUgdGhhdCB0aGUgbXVsdGlsYXllcnMgYXBwcm9hY2ggZ2l2ZSB1cyB0aGUgaGlnaGVzdCBhY2N1cmFjeSB3aXRoIHJlbGF0aW9uIHRvIEFVQy4gYWRkaXRpb25hbGx5IGNvbXBhcmluZyBPbmUtSGlkZGVuX0xheWVyIHdpdGggVHdvLUhpZGRlbi1MYXllcnMsIHNob3dzIHRoZSBmb2xsb3dpbmdzOg0KDQoNCiAgKyAqKk9uZS1IaWRkZW4tTGF5ZXIqKg0KICANCnwgKipIaWRkZW4qKiAgfCAqKkFjY3VyYWN5KiogfCAgKipBVUMqKiB8DQp8Oi0tLS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tLXw6LS0tLS0tLS0tfA0KfCAyIHwgMC45MDY3IHwgMC45NjcxIHwNCiAgICANCiAgKyAqKlR3by1IaWRkZW4tTGF5ZXIqKg0KDQogICANCnwgSGlkZGVuMSAgfCAgSGlkZGVuMiB8ICBBY2N1cmFjeSAgfCAgQVVDICB8DQp8Oi0tLS0tLS0tLXw6LS0tLS0tLS0tfDotLS0tLS0tLS0tLXw6LS0tLS0tfA0KfCAgICAyICAgICB8ICAgICA2ICAgIHwgICAgIDAuOTIgICB8IDAuOTU4NiB8DQogICANCg0KQ29tcGFyaW5nIHRoZSBhYm92ZSwgd2Ugd291bGQgKjwvZm9udD4gICogIDxiPjxmb250IGNvbG9yID0gImJsdWUiPlxjb2xvcntibHVlfSByZWNvbW1lbmQgdXNpbmcgbXVsdGlsYXllcnMgd2l0aCBPbmUgSGlkZGVuIExheWVyLg0K